В предыдущей статье научились создавать дампы и вставлять дампы в исходный код. Попытаемся составить примерный перечень дампов с учетом того, что энергонезависимая память в STM8 пишется/стирается байтами, словами (4 байта), блоками (64 байта) и у областей EEPROM, OPTION, FLASH различные сигнатуры/регистры разрешения записи/стирания и с учетом выбранной скорости 128000 UART:
- Read_128000v14 - этот дамп должен уметь читать все адресное пространство STM8S103F3 от $0000 до $9FFF (раздвинем границу до $EFFF);
- Write_RAM_128000v14 - этот дамп должен уметь писать в адресном пространстве от $0000 до $03FF;
- Copy_RAM_128000v14 - этот дамп должен уметь копировать в адресное пространство от $0000 до $03FF;
- WriteByte_EEPROM_128000v14 - этот дамп должен уметь писать в адресном пространстве от $4000 до $427F;
- WriteWord_EEPROM_128000v14 - этот дамп должен уметь писать в адресном пространстве от $4000 до $427F;
- WriteBlock_EEPROM_128000v14 - этот дамп должен уметь писать в адресном пространстве от $4000 до $427F;
- CopyByte_EEPROM_128000v14 - этот дамп должен уметь копировать в адресное пространство от $4000 до $427F;
- CopyWord_EEPROM_128000v14 - этот дамп должен уметь копировать в адресное пространство от $4000 до $427F;
- CopyBlock_EEPROM_128000v14 - этот дамп должен уметь копировать в адресное пространство от $4000 до $427F;
- WriteByte_OPTION_128000v14 - этот дамп должен уметь писать в адресном пространстве от $4800 до $480A;
- WriteByte_FLASH_128000v14 - этот дамп должен уметь писать в адресном пространстве от $8000 до $9FFF;
- WriteWord_FLASH_128000v14 - этот дамп должен уметь писать в адресном пространстве от $8000 до $9FFF;
- WriteBlock_FLASH_128000v14 - этот дамп должен уметь писать в адресном пространстве от $8000 до $9FFF;
- EraseByte_FLASH_128000v14 - этот дамп должен уметь стирать в адресном пространстве от $8000 до $9FFF;
- EraseWord_FLASH_128000v14 - этот дамп должен уметь стирать в адресном пространстве от $8000 до $9FFF;
- EraseBlock_FLASH_128000v14 - этот дамп должен уметь стирать в адресном пространстве от $8000 до $9FFF;
- CopyByte_FLASH_128000v14 - этот дамп должен уметь копировать в адресное пространство от $8000 до $9FFF;
- CopyWord_FLASH_128000v14 - этот дамп должен уметь копировать в адресное пространство от $8000 до $9FFF;
- CopyBlock_FLASH_128000v14 - этот дамп должен уметь копировать в адресное пространство от $8000 до $9FFF;
Требуется проверка необходимости выравнивания адресов по $xxx0, $xxx4, $xxx8, $xxxC при записи/стирании словами и по $xx00, $xx40, $xx80, $xxC0 при записи/стирании блоками. В адресах $4800 до $480A расположены пары взаимоинверсных OPTION байтов, запись в эту область может изменить скорость работы UART и еще кое-какие препятствия для функционирования начального копировщика boot_FLASH и загрузчика boot_OPTION. Следует не забывать, что в блоке памяти OPTION Bytes (адреса $480B...$483F) находится образ начального загрузчика boot_OPTION, а в адресах $8000...$8003 и $9FF0...$9FFD находится код начального копировщика boot_FLASH. Следует корректно писать в регистровую пару $9FFE:$9FFF с учетом фактического расположения кода/образа прикладной программы во FLASH/EEPROM памяти.
Ввиду ограниченного адресного простраства младших моделей STM8 диапазоном $0000...$9FFF (для STM8S103F3 в частности) старший байт адреса в каждом дампе можно нагрузить дополнительным функционалом при значениях более $9F (раздвинем границу для чтения до $FEFF. Здесь могут быть команды передачи управления прикладной программе, начальному копировщику boot_FLASH, начальному загрузчику boot_OPTION, команды сброса и пр.
Попытаемся сформулировать общий функционал для всех дампов (модулей). Каждый дамп должен уметь:
1 - принимать по UART старший байт адреса и перейти к п.2, если байт является командой перейти на п.4;
2 - отправить по UART эхо старшего байта адреса;
2 - принимать по UART младший байт адреса;
3 - выполнить свой основной функционал и вернуться на п.1;
4 - если команда передачи управления (примем пока $F5), отправить эхо команды, принять старший и младший байты адреса в индексный регист Y, принять старший и младший байты адреса перехода и сохранить их в ячейках $03FE и $03FF, принять размер следующего модуля и пометить его в регистр A, загрузить в индексный регистр X и указатель вершины стека SP величину $03FF, передать управление по адресу в регистровой паре $03FE:$03FF;
5 - если неизвестная команда отправить по UART код ошибки (примем пока $F1) и перейти к п1.
Программа boot_PC при формировании адреса передачи управления должна учитывать :
- для версии $x4 начального загрузчика boot_OPTION (нужен для смены дампа) управление передается на метку boot_OPTION_rx_wait_block: по адресу $0017 (для сохранения скорости 128000 UART) при этом регистр A уже должен содержать размер следующего блока;
- для версии $1x начального копировщика boot_FLASH (нужен для переноса образа из EEPROM в RAM и, возможно, для переноса образа из резервных областей HW регистров в области RAM занятые текущим дампом либо для полного заполнения RAM из резервгых областей HW регистров) управление передается на метку boot_FLASH_copy по адресу $9FF4;
- передача управления по адресу $8000 (вектор сброса) не имеет смысла так как обнуляется указатель вершины стека и начальный копировщик boot_FLAH не сможет перенести код начального загрузчика boot_OPTION в RAM память, для этого мы имеем кнопку сброса;
- прикладная программа в RAM памяти может находиться в диапазоне адресов $0035...$03FF(минус размер критического кода дампа) для сохранения от повреждения кода текущего дампа и начального загрузчика boot_OPTION или во всем диапазоне адресов RAM если удастся воспользоваться временным хранилищем в резервной области HW регистров;
- прикладная программа во FLAH памяти может находиться в диапазоне адресов $8004...$9FEF.
В этой статье создадим класс [Dumps] и наполним его методами и модулями для чтения памяти STM8.
Классы [FileOpenMemorySorting], [IntelHEXfile], [MotorolaS19file].
Файл Dumps.cs :
// Dumps.cs using System; using System.IO; using System.IO.Ports; using System.Threading; using System.Collections.Generic; public class Dumps { public Dumps() { //Load_Dump(ReadByte_128000v14); // переходим на общение с отправленным дампом, меняем скорость COM-порта // sPort.BaudRate = 128000; }// Dumps() public static void Load_First_Dump(byte[] dmp){ // метод вызывается при загрузке первого блока в верхние адреса RAM после нажатия кнопки сброса byte[] dmpToLoad = dmp; Console.WriteLine("Ждем байт 0x14 версии загрузчика. Нажми кнопку сброса на плате."); while(unready) { while(queueBytes.Count == 0){} rx_byte = queueBytes.Dequeue(); //while(sPort.BytesToRead == 0){} rx_byte = (byte)sPort.ReadByte(); // ждем версию загрузчика if(rx_byte == 0x14) { Console.WriteLine("Принят байт 0x{0:X2} от boot_OPTION", rx_byte); byte[] txBytes = {(byte)dmpToLoad.Length}; sPort.Write(txBytes, 0, 1); // отправляем размер дампа unready = false; } else Console.WriteLine("Принят байт 0x{0:X2}. Нажми кнопку сброса на плате.", rx_byte); }//while(unready) sPort.Write(dmpToLoad, 0, dmpToLoad.Length); // отправляем сам дамп while(sPort.BytesToWrite > 0){} Thread.Sleep(500); // подождем пока очистится буфер передачи sPort.BaudRate = 128000; Thread.Sleep(10); Console.WriteLine(); }// Load_first_Dump() public static void Load_Dump(byte[] dmp){ // метод вызывается при замене блока в верхних адресах RAM памяти // необходимо отправить команду загруженному блоку в формате { GO cmd, YH, YL, go_adrH, go_adrL, cntr } err_Msg = "OK"; byte[] dmpToLoad = dmp; go_bytes[0] = 0xF5; // GO cmd команда перехода // go_bytes[1] = 0x00; // YH не для этого метода // go_bytes[2] = 0x00; // YL не для этого метода go_bytes[3] = 0x00; // go_adrH старший байт требуемого адреса boot_OPTION go_bytes[4] = 0x17; // go_adrL младший байт требуемого адреса boot_OPTION go_bytes[5] = (byte)dmpToLoad.Length; // cntr размер следующего к загрузке блока queueBytes.Clear(); // очистим очередь приема sPort.Write(go_bytes, 0, 1); // передаем команду $F5 while(queueBytes.Count == 0){} rx_byte = queueBytes.Dequeue(); if ( rx_byte != go_bytes[0] ){ err_Msg = "Эхо не соответствует команде 0xF5"; return; } sPort.Write(go_bytes, 1, go_bytes.Length-1); // передаем адреса и размер модуля sPort.Write(dmpToLoad, 0, dmpToLoad.Length); // передаем сам модуль while(sPort.BytesToWrite > 0){} Console.WriteLine("Модуль отправлен в boot_OPTION\n"); Thread.Sleep(10); }// Load_Dump() public static SortedDictionaryRead_128000(int adr, int cntr){ err_Msg = "OK"; int addrSpc = adr; // начальный адрес оласти памяти для выгрузки (адрес первого блока) int cntrSpc = cntr; // размер оласти памяти для выгрузки int sizeBlck = 64; // размер блока SortedDictionary AddressByte = new SortedDictionary (); AddressByte.Clear(); if ( addrSpc < 0x0000 | addrSpc >= 0xF000){ err_Msg = "Адрес должен быть в диапазоне 0x0000...0xEFFF"; return AddressByte; } int j = sizeBlck; // размер блока 64 байта queueBytes.Clear(); // очистим очередь приема for (; cntrSpc > 0; ){ if(cntrSpc > 64) j = sizeBlck; else j = cntrSpc; //Console.WriteLine(" ${0:X4}", addrSpc); //queueBytes.Clear(); // очистим очередь приема tx2Bytes[0] = (byte)(addrSpc>>8); tx2Bytes[1] = (byte)addrSpc; sPort.Write(tx2Bytes, 0, 1); // отправляем старший байт адрес блока / команду while(queueBytes.Count == 0){} rx_byte = queueBytes.Dequeue(); //Console.WriteLine("Принят байт 0x{0:X2}.", rx_byte); if ( rx_byte != tx2Bytes[0] ){ err_Msg = "Эхо не соответствует старшему байту адреса"; return AddressByte;} sPort.Write(tx2Bytes, 1, 1); // отправляем младший байт адрес блока tx2Bytes[0] = (byte)j; sPort.Write(tx2Bytes, 0, 1); // отправляем размер блока while(sPort.BytesToWrite > 0){} for (; j > 0x0000; addrSpc++){ // принимаем 64 байта while(queueBytes.Count == 0){} rx_byte = queueBytes.Dequeue(); //Console.WriteLine(" ${0:X4} ${1:X2} {2}", addrSpc, rx_byte, cntrSpc); AddressByte.Add(addrSpc, rx_byte); cntrSpc--; j--; } Thread.Sleep(100); } //Console.WriteLine(); return AddressByte; }// ReadBlock_128000() public static void COM_port_Open(){ // инициализация COM порта if(SerialPort.GetPortNames().Length == 0) { Console.WriteLine("COM порты не найдены"); Console.ReadKey(); return;} Console.WriteLine("Доступны COM порты:"); foreach (string s in SerialPort.GetPortNames()) { Console.WriteLine(" {0}", s); portName = s; } // создаем экземпляр COM-порта с настройками для общения с начальным загрузчиком sPort = new SerialPort(portName, 9600, Parity.None, 8, StopBits.One); try { sPort.Open(); //открываем COM порт } catch (ThreadAbortException) {Console.WriteLine("sPort.Open() ThreadAbortException"); } catch (IOException) {Console.WriteLine("sPort.Open() IOException"); } catch (TimeoutException) {Console.WriteLine("sPort.Open() TimeoutException"); } catch (Exception ex) { Console.WriteLine("sPort.Open() Exception" + ex.Message + "\n: read()"); } queueBytes = new Queue (); queueBytes.Clear(); } public static void COM_port_Close(){ //readThread.Join(); // останавливаем поток чтения COM порта sPort.Close(); // закрываем COM порт } public static void Read() { while (inCycle) { if (sPort.IsOpen) { try { if(sPort.BytesToRead > 0){ queueBytes.Enqueue((byte)sPort.ReadByte()); //Console.WriteLine((byte)sPort.ReadByte()); } } catch (TimeoutException) {Console.WriteLine("Read. TimeoutException"); } } //} } }// Read() public static void Dump_To_Console(SortedDictionary rxAB){ SortedDictionary AddressByte = new SortedDictionary (rxAB); if(Dumps.err_Msg == "OK"){ Console.Write(Dumps.err_Msg); int i = 0; int predAdr = 0; foreach( KeyValuePair kvp in AddressByte){ if (i == 0) { Console.Write("\n ${0:X4} ", kvp.Key); predAdr = kvp.Key;} else { if( (kvp.Key % 16) == 0 | (kvp.Key - predAdr) > 1) Console.Write("\n ${0:X4} ", kvp.Key); } Console.Write("${0:X2} ", kvp.Value); i++; predAdr = kvp.Key; }// foreach }else Console.WriteLine("\n" + Dumps.err_Msg); Console.WriteLine("\n"); } public readonly static byte[] Read_128000v14 = { 0x00, 0x00, 0xFE, 0x03, 0xCC, 0x72, 0x94, 0xFF, 0x03, 0xAE, 0x31, 0x52, 0xC6, 0xFB, 0x30, 0x52, 0x0B, 0x72, 0xFF, 0x03, 0x31, 0x52, 0x55, 0xFB, 0x30, 0x52, 0x0B, 0x72, 0xFE, 0x03, 0x31, 0x52, 0x55, 0xFB, 0x30, 0x52, 0x0B, 0x72, 0x95, 0x90, 0x31, 0x52, 0xC6, 0xFB, 0x30, 0x52, 0x0B, 0x72, 0x95, 0x90, 0x31, 0x52, 0xC6, 0xFB, 0x30, 0x52, 0x0B, 0x72, 0xFB, 0x30, 0x52, 0x0F, 0x72, 0x31, 0x52, 0xC7, 0xA5, 0x20, 0xF7, 0x30, 0x52, 0x0F, 0x72, 0x31, 0x52, 0xF1, 0x35, 0x0B, 0x27, 0xF5, 0xA1, 0xB4, 0x20, 0x08, 0x50, 0x1B, 0x72, 0x07, 0x50, 0x1B, 0x72, 0xF2, 0x26, 0x5A, 0x90, 0x5C, 0xFB, 0x30, 0x52, 0x0F, 0x72, 0x31, 0x52, 0xC7, 0xF6, 0x08, 0x50, 0x1A, 0x72, 0x07, 0x50, 0x1A, 0x72, 0x97, 0x90, 0x31, 0x52, 0xC6, 0x95, 0x90, 0x00, 0xA6, 0xFB, 0x30, 0x52, 0x0B, 0x72, 0x97, 0x31, 0x52, 0xC6, 0xFB, 0x30, 0x52, 0x0B, 0x72, 0xFB, 0x30, 0x52, 0x0F, 0x72, 0x31, 0x52, 0xC7, 0x95, 0x40, 0x22, 0xEF, 0xA1, 0x31, 0x52, 0xC6, 0xFB, 0x30, 0x52, 0x0B, 0x72, 0x35, 0x52, 0x0C, 0x35, 0x32, 0x52, 0x01, 0x35, 0x33, 0x52, 0x00, 0x35 }; public static bool inCycle = true; public static Queue queueBytes; public static Thread readThread; public static SerialPort sPort; public static string portName; public static SortedDictionary rxAddressByte; //public static ArrayList byteArrayList; public static bool unready = true; public static byte rx_byte; public static string err_Msg = "OK"; public static byte[] rx1Byte = new byte[1]; public static byte[] tx2Bytes = new byte[2]; public static byte[] rx4Bytes = new byte[4]; public static byte[] rx64Bytes = new byte[64]; public static byte[] tx64Bytes = new byte[64]; public static byte[] go_bytes = { 0xF5, 0x00, 0x00, 0x00, 0x17, 0x00}; // { GO cmd, YH, YL, go_adrH, go_adrL, cntr } }// class Dumps // Dumps.cs
Файл STM8uLoader.cs :
// STM8uLoader.cs using System; using System.IO; using System.IO.Ports; using System.Threading; using System.Collections.Generic; public class STM8uLoader { public static void Main() { //Dumps myDumps = new Dumps(); Dumps.COM_port_Open(); readThread = new Thread(Dumps.Read); readThread.IsBackground = true; // не позволит остаться потоку в памяти после закрытия программы ? try { readThread.Start(); // запускаем поток чтения COM порта } catch (Exception ex) {Console.WriteLine("readThread.Start(). Exception" + ex.Message); } Dumps.Load_First_Dump(Dumps.Read_128000v14); blckAdr = 0x9FFE; blckSize = 2; Console.WriteLine("Адрес прикладной программы"); Dumps.Damp_To_Console(Dumps.Read_128000(blckAdr, blckSize)); blckAdr = 0x8000; blckSize = 4; Console.WriteLine("Копировщик boot_FLASH"); Dumps.Damp_To_Console(Dumps.Read_128000(blckAdr, blckSize)); blckAdr = 0x0000; blckSize = 53; Console.WriteLine("Код загрузчика boot_OPTION"); Dumps.Damp_To_Console(Dumps.Read_128000(blckAdr, blckSize)); Dumps.Load_Dump(Dumps.Read_128000v14); blckAdr = 0x480B; blckSize = 53; Console.WriteLine("Образ кода загрузчика boot_OPTION"); Dumps.Damp_To_Console(Dumps.Read_128000(blckAdr, blckSize)); blckAdr = 0x9FE0; blckSize =32; Console.WriteLine("Код копировщика boot_FLASH"); Dumps.Damp_To_Console(Dumps.Read_128000(blckAdr, blckSize)); blckAdr = 0xA000; blckSize =64; Console.WriteLine("Заглянем за \"горизонт\""); Dumps.Damp_To_Console(Dumps.Read_128000(blckAdr, blckSize)); readThread.Join(); // останавливаем поток чтения COM порта Dumps.COM_port_Close(); Console.ReadKey(); return; } // Main(); public static Thread readThread; public static string stringMemoryMap = ""; public static byte rx_byte = 0x00; public static byte[] btBytes = new byte[1]; public static byte[] wrd4Bytes = new byte[4]; public static byte[] blck64Bytes = new byte[64]; public static int blckAdr; public static int blckSize; public static bool unready = true; public static string fileName; public static byte[] txBytes = {0, 0}; }// class STM8uLoader // STM8uLoader.cs
BAT файл здесь не требуется. Запускаем файл [STM8uLoader.exe], нажимаем кнопку сброса на плате STM8S103F3.
Исходники:[boot_PC.zip] , [Read_128000v14.asm.zip] .