Notice: Memcache::get(): Server localhost (tcp 11211) failed with: Connection refused (111) in /home/a146/www/we.easyelectronics.ru/engine/lib/external/DklabCache/Zend/Cache/Backend/Memcached.php on line 134
Bootloader для STM8. Прошивка "по воздуху" / STM8 / Сообщество EasyElectronics.ru

Bootloader для STM8. Прошивка "по воздуху"

Представленные ниже материалы не претендуют на научную новизну и новаторство, а являются просто кратким обзором моей реализации бутлоадера для STM8 для прошивки из внешней EEPROM. В конце статьи приложен рабочий проект под IAR. Основано (скорее даже «содрано») все на AppNote от ST. Надеюсь, что кому-то поможет быстрее реализовать некоторые задумки.

Предисловие
Делал я недавно приборчик с Wi-Fi модулем на борту. Хост контроллер там стоит STM8S. Ну и встал вопрос обновления прошивки МК «по воздуху».
Обычно везде описывается вариант работы бутлоадера «на лету» с управлением от некой компьютерной утилиты, которая по какому-либо интерфейсу (UART, SPI, I2C) засылает фрагмент прошивки в МК, этот фрагмент заливается во flash, далее идет следующий фрагмент… и так пока не зальем всю прошивку. Если в процессе прошивки произошел какой-либо сбой (вырубился свет, отказал канал связи), мы спокойно этот сбой устраняем, запускаем bootloader и повторяем попытку. Автономность загрузчика в большинстве случаев ограничивается проверкой условия входа в загрузчик.

Но в некоторых случаях (например, в моем) этот вариант не катит. Например, когда нет прямого доступа к интерфейсам МК, потому что связь с ним (МК) осуществляется через некий канал связи (GSM-модуль, Wi-Fi модуль, PLC). Тогда если произошел сбой в процессе прошивки, то при следующем запуске уже некому будет установить этот самый канал связи т.к. скорее всего bootloader не потянет функционала по управлению вышеперечисленными устройствами.
Напрашивается идея сначала силами основной прошивки «сливать» «образ» новой версии куда-то (например во внешнюю EEPROM), а потом уже загрузиться под бутом и прошиваться с любым количеством попыток, т.к. даже если какой-то сбой и произойдет — из EEPROM образ прошивки никуда не денется.


Не путать!!!
Если вы искали русскоязычную информацию о загрузчике для STM8, то наверняка вам знаком цикл статей от уважаемого angel5a . Лично я много интересного почерпнул из них.
Однако, не смотря на то что оба варианта основаны на одних и тех же AppNote и исходниках от ST, они имеют несколько разные цели. Поэтому, между нашими вариантами есть очень важные отличия:
1) Мой бут располагается по адресу 0х8000, как это предлагается в аппнотах ST
2) Пользовательская прошивка располагается по адресу 0х9000
3) Как следствие пункта 2 — пользовательскую прошивку для моего бута нужно «приготовить». Подробнее ниже.
4) Для моего бута в общем случае нельзя использовать любую терминальную программу. Строго говоря, для него вообще не нужна никакая программа, т.к. «образ» прошивки он берет из EEPROM, находящуюся на одной плате с нашим МК… а вот как этот «образ» окажется в EEPROM выходит за рамки данной статьи))). Понятно, что его туда должна поместить предыдущая версия пользовательской прошивки.

По большому счету, я просто убрал из AppNote ST часть кода отвечающую за обмен с компьютерной утилитой и заменил его на функционал по считыванию «образа» прошивки из внешней EEPROM.

Организация «образа» прошивки
Образ у меня сливается и хранится во внешней EEPROM 24xxxx
Адреса 0х00 и 0х01 отведены для флага необходимости перепрошивки. Пользовательское приложение его при необходимости выставляет и передает управление буту. Бут анализирует этот флаг и при его наличии начинает перепрошивку. После успешной перепрошивки бут этот флаг сбрасывает.
Адреса 0х02 и 0х03 отведены для хранения размера новой прошивки.
Адреса 0х04 и далее отведены для хранения самой прошивки

Процесс прошивки
Сначала проверяем требуется ли вообще нам сейчас перепрошивка с помощью флага. Я выбрал что флаг считается установленным если по адресам EEPROM 0х00 и 0х01 лежат значения 0x47 и 0x4F соответственно. В противном случае флаг считается сброшенным.
Если флаг сброшен то проверяется корректность прошивки. Признаком считается наличие по адресу 0х9000 значения 0x82 или 0xAC. Если прошивка признана некорректной бут все равно запускается.

boot_flag = (u16)((EE_Read(0) << 8) + EE_Read(1));  // вычитываем флаг необходимости перепрошивки
if( boot_flag != 0x474F )  // если флаг не равен 0x474F (просто я выбрал такое значение)...
{
   //... то проверяем на наличие хоть какой-нибудь прошивки по адресу 0х9000
   if((*((u8 FAR*)MainUserApplication)==0x82) || (*((u8 FAR*)MainUserApplication)==0xAC))
   {
      //если прошивка есть - выходим из бута передав управление пользовательской прошивке
      asm("LDW X,  SP ");
      asm("LD  A,  $FF");
      asm("LD  XL, A  ");
      asm("LDW SP, X  ");
      asm("JPF $9000");
   }
}
// сюда попадаем если флаг равен 0x474F и требуется приступить к перепрошивке
// или если по адресу 0х9000 лежит какая-то ерунда, т.е. прошивка слетела

Если установлен флаг прошивки либо текущая прошивка признана некорректной, то переходим непосредственно к обновлению прошивки.

Сначала считываем размер прошивки, затем порционно вытягиваем её из EEPROM и заливаем во flash. Для непосредственной заливки во flash использованы исходники от ST в чистом виде.
Я гружу порциями по 256 байт + последняя порция сколько останется. Для ускорения процесса можно увеличить порции, для этого все цифры 256 в представленном ниже куске нужно заменить на бОльшую.
После успешной прошивки обязательно сбрасываем флаг и можем выходить из бута тем же способом что и выше

boot_length = (u16)((EE_Read(2) << 8) + EE_Read(3));  // считываем размер прошивки
full = (u8)(boot_length / 256);   // вычисляем число полных пакетов (по 256 байт)
remainder = (u8)(boot_length % 256);  // вычисляем размер последнего пакета (который менее 256 байт)
DataAddress = *(u8 FAR**)(&Flash_Address[0]);

/*Вычитываем из EEPROM и грузим во flash пакеты размером 256 байт */
for(u8 i = 0; i < full; ++i)
{
   for(u16 j = 0; j < 256; ++j)
      DataBuffer[j] = EE_Read(0x0004 + i*256 + j);  // считываем очередную порцию в 256 из EEPROM в буфер
   WriteBuffer(DataAddress, 256);  // заливаем буфер во flash по адресу DataAddress
   Increase_Address(256);          // увеличиваем значение DataAddress
}

/*Вычитываем из EEPROM и грузим во flash оставшийся довесок, если он остался конечно */
if(remainder != 0)
{
   for(u16 j = 0; j < 256; ++j)
      DataBuffer[j] = EE_Read(0x0004 + full*256 + j);  // считываем последнюю порцию прошивки из EEPROM
   WriteBuffer(DataAddress, remainder);   // и заливаем её во flash
}
EE_Write("\x00\x00", 0, 2, 0x0002);  // обязательно сбрасываем флаг перепрошивки

Функции, связанные с настройкой I2C и чтением из EEPROM, вынесены в отдельные файлы, поэтому их легко можно заменить на свои. Желательно конечно использовать не по-байтовое чтение из EEPROM как у меня, а блочное. Просто у меня с ним как-то не сложилось, потому сделал как есть.

Ну и вот код функции увеличения адреса:

void Increase_Address(u16 shift)
{
  u16 address;
  address = (u16)((Flash_Address[1] << 8) + Flash_Address[2]);
  address += shift;
  Flash_Address[2] = (u8)(address);
  Flash_Address[1] = (u8)(address >> 8);
  DataAddress = *(u8 FAR**)(&Flash_Address[0]);
}


Подготовка пользовательской прошивки
Описываемые тут действия необходимо производить не с проектом бутлоадера, а с проектом, который должен быть залит при помощи бутлоадера, т.е. с пользовательской прошивкой.

Тут всё очень просто — нужно взять исходный *.icf файл для вашего МК и чуток изменить его что бы при компиляции линкер расположил пользовательскую прошивку по адресу 0х9000.
Оригинальный icf-файл ищем в папке с IARом \IAR Systems\Embedded Workbench 6.5\stm8\config

Покажу только что нужно откорректировать относительно оригинального файла.
Оригинальный icf-файл:

define region NearFuncCode = [from 0x8000 to 0xFFFF];

define region FarFuncCode = [from 0x8000 to 0xFFFF];

define region HugeFuncCode = [from 0x8000 to 0xFFFF];

...

place at start of NearFuncCode  { block INTVEC };

Откорректированный icf-файл:

define region VectorRegion = [from 0x9000 to 0x907F];

define region NearFuncCode = [from 0x9080 to 0xFFFF];

define region FarFuncCode = [from 0x9080 to 0xFFFF];

define region HugeFuncCode = [from 0x9080 to 0xFFFF];

...

place at start of VectorRegion  { block INTVEC };

Лучше конечно оригинал не трогать, а сделать его копию. В таком случае местоположение файла нужно указать линкеру в опциях проекта:


НЕбыстрый старт
Есть одно важное западло. Если просто залить этот бут в МК то он сразу попытается его (МК) прошить. Но в EEPROM у нас пока что ничего и нету. А если залить «приготовленную» по рецепту выше прошивку, то работать она не будет ибо вектор прерываний пуст. Поэтому сначала нужно залить и прошивку и бут одновременно. К счастью это позволяет сделать STVP.
Сначала нужно в меню выбрать Edit -> Preferences и снять галочку «Erase device memory before programming»

Затем сразу друг за другом открыть 2 наших хекса (бут и свою «приготовленную» прошивку).
Если после этого посмотреть основное окно то увидите что с адреса 0х8000 до какого-то идут данные (это бут) затем до адреса 0х8FFF идут 0х00 и с адреса 0х9000 опять данные (это ваша прошивка).
Можно заливать и получите устройство способное к прошивке по воздуху. Естественно что ваша пользовательская прошивка должна уметь заливать образ в новой версии в EEPROM.

Рассуждения на тему и прочий флуд
1) Прошивку размером около 19К льет секунд за 10. Имеется ввиду только вычитывание её из EEPROM и сам процесс прошивки. Образ уже там.
2) Для повышения надежности можно после каждого блока (порции) прошивки держать и сверять контрольную сумму этого блока.
3) Значительного ускорения прошивки можно добиться заменив по-байтовое чтение из EEPROM на блочное.
4) В комментариях подсказали очень интересную идею. Вместо EEPROM использовать для хранения прошивки оставшийся flash микроконтроллера. Разумеется, что в таком случае размер flash должен быть минимум в 2 раза больше прошивки.
5) Для еще пущей надежности, дополнительно можно хранить резервную (заводскую) прошивку на «всякий пожарный» и вести счетчик неудачных попыток. В моем проекте это не канает, т.к. прибор бытовой и массовый, рассчитанный на «простого» пользователя. Управляющая утилита, не дает пользователю вручную баловаться с прошивками, а просто при выходе новой версии скачивает её из интернета и лишь спрашивает пользователя «установить или нет?»
  • +7
  • 11 июля 2013, 18:48
  • Den1s
  • 1
Файлы в топике: STM8_AirBootloader.zip

Комментарии (25)

RSS свернуть / развернуть
Для дополнительной надежности (ЭППЗУ тоже не безгрешно) предлагаю держать еще в запасе (в идеале в памяти только для чтения) базовую версию прошивки, которую загрузчик восстановит после неудачи со свежей прошивкой. Так устройство сможет повторить загрузку с сервера новой прошивки еще раз.
+3
Всеми руками поддерживаю — всегда есть человеческий фактор, благодаря которому зашьётся прошивка, которая будет неработоспособна. Лично я у себя решил таки впихнуть код работы с GPRS модемом в бутлоадер, и то несколько раз получал кирпичи…
0
Впихнуть управление каналом связи в бутлоадер — это конечно идеальный вариант. Но я по исходил из того что не впихнется))
0
Так и не надо — получается дублирование функционала. Просто в заначке надо держать заводскую версию софта, которую можно восстановить в случае неудачного апдейта. Или же как пример баг в новой прошивке, а клиенту срочно надо рабочее устройство. Откатываемся к первой версии и спокойно работаем.
0
Если размер прошивки в несколько раз меньше, чем размер флеша контроллера, то можно хвост флеша этого использовать для хранения временной (загруженной по сети, например) или резервной прошивки =)
+2
Интересная идея, мне как-то в голову не приходило))
0
Подкорректирую. Для резервной лучше не в конце флеша, а в бут-области (сдвинув прошивку ещё дальше соответственно). Это позволит защитить резервную прошивку от случайной порчи с помощью встроенной защиты бутлодыря.
0
Ну ведь размер защищенной части всего 0х1000 байт (4К). И часть из них занята самим бутом. Маловероятно что базовая влезет.
0
RM0016^
Рис9 Low density имеет UBC до 8КБ (даташит 103ф3 таблица 12 то же самое утверждает)
Рис10 Medium density до 32КБ имеет UBC до 32КБ
Рис10 High density до 128КБ имеет UBC до 128КБ
Т.е. в общем случае вся флешка может быть защищена. Только соответствующий Option Byte надо правильно выставить.
0
Как бы cut…
А вообще — я когда читал статейку о самопрограммировании STM32, как раз о таком варианте подумал. А тут — раз! И он уже есть ))) Только для STM8, но главное — принцип.
+2
Можно и иначе сделать. Если размер внешней ЕЕПРОМки равен размеру флеша МК, то можно просто загружать в него прошивку как есть, без заголовка (который хранит флаг необходимости прошивки и размер). При запуске сверять содержимое ЕЕПРОМ и флеша и при несовпадении — прошивать.
-1
  • avatar
  • Vga
  • 11 июля 2013, 21:20
Можно много чего придумать, и у меня идеи еще тоже были. Но инженера нужно одергивать иногда, иначе фантазия его далеко заведет. Идеи по повышению надежности системы у меня есть, но пока на испытаниях все работает как часы. Сейчас приборы готовятся в серию. Если по первой партии будет много косяков буду апгрейдить))
+1
Перенес в стм8
0
Уж простите за занудство и теорию без практики, но тут:
Для ускорения процесса можно увеличить порции, для этого все цифры 256 в представленном ниже куске нужно заменить на бОльшую.
несколько сомневаюсь.
У вас чтение EEPROM идет побайтово с абсолютными адресами. Каждая операция чтения займет:
адрес/Зап + адреса данных + адрес/Чт + данный => 4 байта при еепром до 2кБ, иначе 5 байт => 39/48 бит => от 98/120 мкс (400кГц) до 390/480 мкс (при 100кГц — но тут надо помнить про еррату и 80кГц).
Тогда для чтения одного блока в лучшем случае надо 64 * 0.1мс = 6.4мс = время программирования страницы (6.6мс для 103ф3). Остальные накладные расходы (вызовы функций, дополнительные проверки), будут составлять достаточно малую часть времени, тем более при увеличении от 256.
Явно не хватает блочного чтения памяти (или хотя бы чтения очередного элемента, без предварительной установки адреса — RandomRead названа с примерах i2c вроде). Такая оптимизация позволит значительно (2 и более раза) ускорить процесс программирования.
0
Про блочное чтение забыл. Согласен. Признаюсь не сумел я освоить блочное чтение, детали сейчас не помню, но что-то не вышло и «временно» сделал по-байтово. Ну а когда заработало, занялся другими делами и как обычно «временное» стало вполне «постоянным». Сейчас добавлю в конец топика об этом.
0
Там у STM8/STM32 надо внимательно ман и еррату читать для I2C — кривая мросто жуть. В частности 1, 2 и >2 байт по разному обрабатываются. Лучше взять пример «оптимальные функции i2c» (не помню сейчас какой аппноут) и на их основе делать (или их и взять).
Так же почитайте еррату, там есть про блокировку шины (BUSY флаг) и потерю бита (конкретная ревизия МК).
У самой еепромки при записи надо учитывать страницы, при чтении вроде есть переход между страницами (не помню уже). Так что вся проблема в МК.
0
Давно использую процессор с двойным размером flash. Новую версию записываю в верхнюю часть. При старте проверяет новую версию по crc16/crc32 и копирует в рабочую область. Потом проверяет рабочую версию и херит новую. Так что вероятность сбоев минимальна. Без проверки crc никогда не использую в пром устройствах. При проводном соединении (CAN, RS485, UART и т.п.) сам bootloader поддерживает загрузку с хоста. Но правда STM32 в основном или EFM32.
+1
Используем похожий макар у нас, только вместо еепрома флешка на 16 мбит — надо хранить прошивку для себя, для панельки управления (там отдельный девайс на rs485) и еще мег остается в запасе на вырост. Только там еще до кучи своя примитивная файловая система — первая страничка флеша с данными о том, что вообще на флеше есть, 2-4 страницы — метаданные самих прошивок: флаг о том, что надо прошицца, размер прошивки, CRC, начало файла итд. И со следующего блока начинаются сами бинарники.
+ я бы на самом деле в бутлоадер вставил проверку удачной записи и какой-нибудь жлементарный счетчик, чтобы не делать больше трех попыток.
+1
Со счетччиком хорошая мысль. Правда не было такого что бы в цикл впадал бутлодер, но есть такой вариант. Только не счетччик и биты обнулять при каждой попытке. Байт — 8 попыток. Хорошая идея — спасибо!
0
Поэтому сначала нужно залить и прошивку и бут одновременно. К счастью это позволяет сделать STVP.
… это же позволяет и ИАР
+1
Прикольно, не знал. Попробую сам и добавлю в статью
0
Какой wiFi модуль использовали? Я пытал микрочиповский, так он и не захотел нормально интернет-радио обслуживать, часто затыткался.
0
WizFi210 от WizNet. Вообще думал сделать пару тройку статеек про него т.к. довольно неплохо его освоил. А русской инфы о нем и нет, да и родная документация хреновенькая… если интерес конечно есть))
0
В appnote написано, что необходимо делать редирект с основной таблицы прерываний (которая в бутлоадере по адресу 0x8000) в таблицу для программы (например, 0х9000). Не понятно как реализуется редирект в данной статье (конретно в программе bootloader'a), где объясняется таблице векторов по адресу 0х8000 куда ей делать редирект. Я сделал вручную переход в каждом прерывании на соответствующий в новую таблицу, вот так:
...
INTERRUPT_HANDLER(TIM2_UPD_OVF_BRK_IRQHandler, 13)
{
  asm("JPF $903C");
}
...

Я правильно рассуждаю и делаю редирект, или что-то упускаю? Если делаю как в статье, то редиректа нету.
0
Я вместо флага нужности прошивки делал проверку crc и если они не совпадают, то запускается лоадер.
Правда у меня было получение прошивки с внешнего мастера и были признаки входа в загрузчик и выхода из него, если обновление было отменено или закончилось неудачно.
0
  • avatar
  • PRC
  • 11 апреля 2017, 08:54
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.