|
|
  |
STM32 bootloader, написание собственного бута |
|
|
|
Jan 3 2012, 13:31
|

Участник

Группа: Участник
Сообщений: 32
Регистрация: 3-02-11
Из: Украина, Киев
Пользователь №: 62 695

|
Всем привет. Появилась задача создать бутлоадер, который будет удаленно перепрошивать контроллер. Контроллер работает в связке с gsm-модулем. Раньше опыта создания бутов не было, поэтому вопросов появилось просто масса. Во-первых, для себя я вижу 2 концепции бутлоадера, каждый со своими плюсами и минусами. 1. Основная программа качает прошивку, пишет её в определенное место флеша(например, с 16-й страницы флеша), проверяет, все ли правильно записалось, устанавливает в энергонезависимом регистре флаг, что нужно войти в бут, перезагружается, происходит вход в бутлоадер, который очищает основную программу(например, со 2й страницы флеша) и перезаписывает новую прошивку на это место и переходит на выполнение основной программы. Преимущества(+)/недостатки(-): +простота бутлоадера, в том числе не нужно инициализировать юарт и модуль из бутлоадера. +не нужно долго висеть в буте +перезагружаться в бут можно только после того, как прошивка успешно закачана -если не верно закачалась прошивка либо же не рабочая прошивка - только вручную перепрошивать -нужен МК с бОльшим обьемом Flash 2. Основная программа перегружает МК в бут, который удаляет старую прошивку, качает новую и сразу записывает её вместо старой. Преимущества(+)/недостатки(-): +бут может сам скачать новую прошивку +контроллер с меньшим обьемом флеша -сложность бута - нужно будет иниициализировать юарт, включать и инициализировать gsm модуль. -долго находиться в бутлоадере Какой вариант лучше? И, может, есть более совершенные решения? Теперь вопросы по реализации: 1. Какие подводные камни могут быть в написании бута? 2. Читал на форуме про то, что нужно перезаписывать таблицу векторов(или вектора прерываний?)? Можно об этом подробнее? Как это делается? Где об этом можно подробнее почитать, а то никакой вразумительной инфы не нашел. 3. Бут и основная программа пишуться ведь как 2 разных проекта? Отадельным вопросом - как заставить программу перейти на выполнение с определенного адреса? Где найти об этом информацию? Пробывал как в примере от ST: CODE #include "common.h" #define ApplicationAddress 0x08000073
extern pFunction Jump_To_Application; extern uint32_t JumpAddress;
/* Jump to user application */ JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4); Jump_To_Application = (pFunction) JumpAddress; /* Initialize user application's Stack Pointer */ __set_MSP(*(__IO uint32_t*) ApplicationAddress); Jump_To_Application(); Не получилось. На ф-ции Jump_To_Application(); уходит в hardfault Особенно не понятно, где тело этой функции, нигде в присоединенных файлах эта функция не описана. (файлы примера прикрепил к сообщению) Был бы очень благодарен, если бы кто-то подсказал, где об этом можно прочитать и где можно найти рабочие и понятные примеры. И чтобы не плодить сообщения, напишу про еще одну проблему с отладочной платой: на STM32VLDiscovery целевой контроллер работает, прошивается, но невероятно греется. Даже на 2 секунды нельзя на нем задержать палец. Очень горячий. Закороток визуально нет, да и паяльником я не притрагивался к плате , не знаю, с чего все и началось. Тестера под рукой тоже нет. Не работает светодиод LD4. Правда я не знаю, это следствие или причина нагрева, сейчас далеко от цивилизации, протестить и перепаять ничего не могу. (схема : http://www.st.com/internet/com/TECHNICAL_R...CD00267113.pdf)
Прикрепленные файлы
an2557.rar ( 1.64 мегабайт )
Кол-во скачиваний: 79
|
|
|
|
|
Jan 3 2012, 16:12
|
■ ■ ■ ■
    
Группа: Свой
Сообщений: 1 100
Регистрация: 9-08-06
Пользователь №: 19 443

|
Я бы делал вариант 2, т.к. в первом варианте во вновь загруженной прошивке может неожиданно вылезти глюк при загрузке новой пошивки и это "конец".  Да и памяти больше нужно. Хотя конечно глюк может вылезти везде. Переход на приложение: Код void JumpToApplication(Int32U addr) { typedef void (*pFunction)(void); pFunction Jump_To_Application; Int32U JumpAddress; if(addr>=0x08005000) { JumpAddress = *(Int32U*) (addr + 4); Jump_To_Application = (pFunction) JumpAddress; /* Initialize user application's Stack Pointer */ __MSR_MSP(*(vu32*) addr); Jump_To_Application(); } } Цитата Читал на форуме про то, что нужно перезаписывать таблицу векторов(или вектора прерываний?)? NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x08005000); Цитата Бут и основная программа пишуться ведь как 2 разных проекта? Да.
--------------------
Делай что должен и будь что будет.
|
|
|
|
|
Jan 3 2012, 20:52
|
Местный
  
Группа: Свой
Сообщений: 311
Регистрация: 12-01-11
Из: Калининград (Koenigsberg)
Пользователь №: 62 182

|
Цитата(ierofant @ Jan 3 2012, 16:31)  Пробывал как в примере от ST: Не получилось. На ф-ции Jump_To_Application(); уходит в hardfault Внимательно проверьте правильно ли и по правильным ли адресам записывается приложение. Я с этим долго бился на LPC17 из-за банальной ошибке при записи данных. skripach, Ваш пример ничем не отличается от приведённого ТС. Указания нового положения таблицы векторов нет. Или это фича STM? Я правда пока с ними не работал, но предстоит в ближайшее время. Вы ставите указатель на функцию на адрес ResetISR в приложении и обновляете верхушку стека, но не указываете новое расположение таблицы векторов прерываний. Ведь без этого прерывание в приложении приведёт к попутке перехода на вектор в загрузчике.
--------------------
typedef enum { no, yes, maybe } bool; | блог тут
|
|
|
|
|
Jan 3 2012, 21:30
|
■ ■ ■ ■
    
Группа: Свой
Сообщений: 1 100
Регистрация: 9-08-06
Пользователь №: 19 443

|
Цитата skripach, Ваш пример ничем не отличается от приведённого ТС. Возможно, автор вопрошал "как перейти на выполнение с определенного адреса?" я привел функцию из своего загрузчика под stm32. Цитата Указания нового положения таблицы векторов нет. Если правильно помню это сделано в приложении, перед прыжком были выключены все прерывания.
--------------------
Делай что должен и будь что будет.
|
|
|
|
|
Jan 3 2012, 21:33
|

Участник

Группа: Участник
Сообщений: 32
Регистрация: 3-02-11
Из: Украина, Киев
Пользователь №: 62 695

|
Спасибо, что откликнулись. skripachПытаюсь в простейшей программке протестировать переход выполнения кода по определенному адресу. Пробывал с вашим примером, опять не получилось. Вот мой код(для иара, поэтому немного отличается от вашего): CODE #include "stm32f10x.h" #include "core_cm3.h" #define ApplicationAddress 0x08000081
void JumpToApplication(uint32_t addr) { typedef void (*pFunction)(void); pFunction Jump_To_Application; uint32_t JumpAddress; //if(addr>=0x08005000) //{ JumpAddress = *(uint32_t*) (addr + 4); Jump_To_Application = (pFunction) JumpAddress; /* Initialize user application's Stack Pointer */ __set_MSP(*(uint32_t*) addr); //__MSR_MSP(*(vu32*) addr); Jump_To_Application(); //} }
void init_mk() { RCC->APB2ENR |= (RCC_APB2ENR_IOPAEN|RCC_APB2ENR_IOPCEN); GPIOC->CRH |= (GPIO_CRH_MODE9 | GPIO_CRH_MODE8); //C.8, C.9 OUTPUT GPIOC->CRH &= ~(GPIO_CRH_CNF9 | GPIO_CRH_CNF8); GPIOA->CRL&=~GPIO_CRL_MODE0;//A.0 INPUT }
void test() { GPIOC->BSRR = GPIO_BSRR_BS9; }
int main() { init_mk();
JumpToApplication(ApplicationAddress); GPIOC->BSRR = GPIO_BSRR_BS8;
test();
while(1) { } }
__MSR_MSP - не было, заменил на функцию подобного содержания из своего хидера. Вот такая функция CODE void __set_MSP(uint32_t topOfMainStack) { __ASM("msr msp, r0"); __ASM("bx lr"); } Проверка была для конкретно вашего примера, насколько я понял, поэтому она закоменчена. #define ApplicationAddress 0x08000081 - это начальный адрес функции test(). (беру его из map-а) Т.е. по предположению - код должен выполниться в обход строки : GPIOC->BSRR = GPIO_BSRR_BS8; Верно? Отладчик после выполнения Jump_To_Application(); матерится на то, что указатель стека находится вне его пределах, причем откуда такое значение - черт знает: The stack pointer for stack 'CSTACK' (currently 0x00F44F28) is outside the stack range (0x20000000 to 0x20000400) Cosmojam, я пока пытаюсь осуществить просто переход выполнения программы по определенному адресу, таблицу векторов даже не трогаю.  Выше отписался, что у меня не так. Кстати, я правильно понимаю алгоритм работы бутлоадера? После того, как он закончил все свои необходимые действия, нужно: 1. перенести таблицу векторов по адресу в памяти, где начинается основная программа.(т.е. если у меня основная программа записана, начиная с 2й страницы флеша (1 страница - 1кб), то нужно будет сделать так: NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x08000800)  2. Перейти к выполнению кода на тот же адрес (0x08000800) Верно ли я все понял? P.S. Кстати, skripach, спасибо за приведенный код.
Сообщение отредактировал ierofant - Jan 3 2012, 21:38
|
|
|
|
|
Jan 4 2012, 00:49
|
■ ■ ■ ■
    
Группа: Свой
Сообщений: 1 100
Регистрация: 9-08-06
Пользователь №: 19 443

|
Цитата Т.е. по предположению - код должен выполниться в обход строки : GPIOC->BSRR = GPIO_BSRR_BS8; Верно? Разумеется нет. Функция JumpToApplication это не совсем "перейти на выполнение с определенного адреса", это переход на приложение расположенное по адресу [addr]. Если попытаетесь разобраться с содержимым JumpToApplication то многое станет понятно в том числе почему "матерится на то, что указатель стека...". Также советую посмотреть в дизассамблер. Цитата Кстати, я правильно понимаю алгоритм работы бутлоадера? После того, как он закончил все свои необходимые действия, нужно: 1. перенести таблицу векторов по адресу в памяти, где начинается основная программа.(т.е. если у меня основная программа записана, начиная с 2й страницы флеша (1 страница - 1кб), то нужно будет сделать так: NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x08000800) Близко. Таблицу векторов нужно перенести туда где она находится в приложении. Начало приложения и адрес таблицы векторов могут не совпадать, см настройки линкера. Цитата 2. Перейти к выполнению кода на тот же адрес (0x08000800) Нет. Нерейти на начало приложения, в нашем случае адрес начала приложения расположен по адресу (адрес таблицы векторов+4). см. таблицу векторов.
--------------------
Делай что должен и будь что будет.
|
|
|
|
|
Jan 4 2012, 07:47
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Как у вас все сложно! Какова структура "обычной" программы под STM32? В начале идет блок векторов, он состоит из: -начальное значение стека -адрес обработчика исключения Reset -адреса остальных обработчиков ядра -адреса обработчиков периферии Далее за ним идет код. Ну так и работать надо с этой структурой, а не с какими-то магическими числами и приведением указателей: CODE struct application { struct vectors { typedef void( *handler )( void ); uint32_t MSP_init; handler Reset_vector; handler Core_handler[14]; static const uint_fast8_t MCU_VECTORS = #if defined(STM32F10X_LD_VL) || defined(STM32F10X_MD_VL) 56 #elif defined(STM32F10X_HD_VL) 61 #elif defined(STM32F10X_CL) 68 #elif defined(STM32F10X_LD) || defined(STM32F10X_MD) ||defined(STM32F10X_HD) ||defined(STM32F10X_XL) 60 #endif ; handler MCU_handler[MCU_VECTORS]; } Vectors; uint32_t Size; // application size, 4-byte words };
extern const application Application;
....... if (!CRC->DR) // Application Section OK { // set vectors table to application vectors SCB->VTOR = (uintptr_t)&Application.Vectors; asm volatile ( " MSR MSP, %0\n" // store App stack init value to MSP : : "r" (Application.Vectors.MSP_init) ); Application.Vectors.Reset_vector(); } От линкера требуется лишь предоставить символ Application с адресом начала той области, в которую загружается приложение. Не знаю как в последнем ИАРе, в старом это можно было сделать, вписав в линкерный скрипт или командную строку линкера -DApplication=0x08001000 У меня при сборке приложения линкер сразу за таблицей векторов вписывает размер образа, чтобы загрузчик мог просчитать контрольную сумму загруженного приложения (и только его, не учитывая свободную память).
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Jan 8 2012, 00:38
|

Участник

Группа: Участник
Сообщений: 32
Регистрация: 3-02-11
Из: Украина, Киев
Пользователь №: 62 695

|
Спасибо всем за помощь. В целом получилось и перейти на выполнение приложения и перенести таблицу векторов. Вот только если я пытаюсь перенести таблицу в программе бутлоадера - не выходит (не работают прерывания), если же в основной программе - все нормально. Переношу так(т.к. не использую библиотеку для периферии, написал сам) : CODE #define vector_table_offset 0xC00 //смещение #define offset (uint32_t)(vector_table_offset & 0x1FFFFF80) //выравнивание
SCB->VTOR = offset; На приложение перехожу с помощью функции JumpToApplication, которую подсказал skripach. Сергей Борщ, пытался разобрать вашу программу. Не понятна эта строчка: CODE SCB->VTOR = (uintptr_t)&Application.Vectors; Возвращается адрес Application.Vectors, после чего явно преобразуется в тип указателя. Тут понятно. А что именно содержит Application.Vectors? И как оно туда попало? Связано это, видимо, с QUOTE От линкера требуется лишь предоставить символ Application с адресом начала той области, в которую загружается приложение. И на сколько я понимаю, линкер предоставляет структуре адрес начала приложения, потом уже в структуре в соотвествии этому начальному адресу всем векторам присваиваются их адреса? Написана ваша программа, однозначно красивее и четче, единственное, хочется её полностью понять. А может еще подскажете, где в iar 6.21 линкерный скрипт?
|
|
|
|
|
Sep 19 2012, 10:09
|
Гуру
     
Группа: Свой
Сообщений: 2 128
Регистрация: 21-05-06
Пользователь №: 17 322

|
Цитата(Almaz1988 @ Sep 19 2012, 12:41)  Столкнулся с казалось бы элементарной проблемой - не могу прошить даже самую простую программу (моргание светодиодом) по адресу, отличному от нуля. Прошиваться она может и прошивается, но работать не будет. Цитата(Almaz1988 @ Sep 19 2012, 12:41)  Пишу в Keil. Адрес выставляю следующим образом: правый клик по проекту-->Options for target-->Linker-->R/O base = 0x00000100. Попробовал прошить из LPCXpresso, - получил ошибку "vectors still have erased values". Спасибо за ответы Этого недостаточно. Нужна вторая программа (собственно бутлоадер), которая должна быть расположена по адресу 0, и которая запустит основное приложение. Зашить её нужно до зашивки-отладки основной программы. Ещё момент: смещение 0x100 может и допустимо, но нежелательно - flash стирается по 4кБ за раз и получается нельзя обновить основную программу, не стирая бут. Стереть его можно, если он будет работать из ОЗУ, но это чревато. Ставьте смещение кратное 0x1000. Для простого бута 4к достаточно, если нужно больше - увеличивайте. P.S. ещё про boot на lpc11xx
|
|
|
|
|
Sep 19 2012, 10:55
|
Частый гость
 
Группа: Участник
Сообщений: 100
Регистрация: 19-09-12
Пользователь №: 73 602

|
Спасибо Артем, ваш ответ очень помог)
По адресу 0х0000 2000 залил "Рабочая программа", которая моргает 4 раза.
По адресу 0х0000 0000 тоже залил "Бутлоадер", которая моргает 4 раза, а потом прыгает по адресу 0х2169 (функция main() "Рабочей программы" по MAP-файлу).
Все сработало как и ожидалось - МК моргнул 8 раз. Т.е. МК стартовал с "Бутлоадера" затем передал управление "Рабочей программе".
Но вот на следующем этапе на место "Рабочей программы" я залил программу по сложнее ( которую собственно говоря и пишу). В итоге МК моргнул 4 раза, а "Рабочая программа" не запустилась. Подозрения падают на то, что она работает по прерываниям, о чем, собственно, ваша ссылка.
Не могли вы "на пальцах" объяснить, почему эти прерывания требуют особого отношения и для чего нужен ремап векторов?
|
|
|
|
|
Sep 19 2012, 11:56
|
Гуру
     
Группа: Свой
Сообщений: 2 128
Регистрация: 21-05-06
Пользователь №: 17 322

|
Цитата(Almaz1988 @ Sep 19 2012, 13:55)  Не могли вы "на пальцах" объяснить, почему эти прерывания требуют особого отношения и для чего нужен ремап векторов? Ремап нужен чтобы и бут и рабочая программа могли использовать прерывания. К тому же вектора расположены по адресам от 4 и получается что приложение хранит адреса своих обработчиков в области бута, что как-то странно (если без ремапа). Если буту прерывания не нужно, то можете посмотреть пример NXP (secondary bootloader). там пример где все обработчики делают jump (или call - не помню точно) в область рабочей программы. Цитата(Almaz1988 @ Sep 19 2012, 13:55)  По адресу 0х0000 0000 тоже залил "Бутлоадер", которая моргает 4 раза, а потом прыгает по адресу 0х2169 (функция main() "Рабочей программы" по MAP-файлу). P.S. Бут должен "прыгать" не на main, а на Reset_Handler.
|
|
|
|
|
Sep 20 2012, 04:24
|
Частый гость
 
Группа: Участник
Сообщений: 100
Регистрация: 19-09-12
Пользователь №: 73 602

|
Цитата(Сергей Борщ @ Sep 20 2012, 00:15)  Если мы говорим о Cortex-M3 (M4), то нужно записать в регистр VTOR значение 0x00002000, ведь именно по этому адресу начинается таблица векторов вашего приложения (ячека с начальным значением стека также входит в эту таблицу). Cortex-M0
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|