Перейти к содержанию
    

Всем привет.

 

Появилась задача создать бутлоадер, который будет удаленно перепрошивать контроллер. Контроллер работает в связке с gsm-модулем.

Раньше опыта создания бутов не было, поэтому вопросов появилось просто масса.

 

Во-первых, для себя я вижу 2 концепции бутлоадера, каждый со своими плюсами и минусами.

 

1. Основная программа качает прошивку, пишет её в определенное место флеша(например, с 16-й страницы флеша), проверяет, все ли правильно записалось, устанавливает в энергонезависимом регистре флаг, что нужно войти в бут, перезагружается, происходит вход в бутлоадер, который очищает основную программу(например, со 2й страницы флеша) и перезаписывает новую прошивку на это место и переходит на выполнение основной программы.

 

Преимущества(+)/недостатки(-):

+простота бутлоадера, в том числе не нужно инициализировать юарт и модуль из бутлоадера.

+не нужно долго висеть в буте

+перезагружаться в бут можно только после того, как прошивка успешно закачана

-если не верно закачалась прошивка либо же не рабочая прошивка - только вручную перепрошивать

-нужен МК с бОльшим обьемом Flash

 

2. Основная программа перегружает МК в бут, который удаляет старую прошивку, качает новую и сразу записывает её вместо старой.

 

Преимущества(+)/недостатки(-):

+бут может сам скачать новую прошивку

+контроллер с меньшим обьемом флеша

-сложность бута - нужно будет иниициализировать юарт, включать и инициализировать gsm модуль.

-долго находиться в бутлоадере

 

Какой вариант лучше? И, может, есть более совершенные решения?

 

Теперь вопросы по реализации:

1. Какие подводные камни могут быть в написании бута?

2. Читал на форуме про то, что нужно перезаписывать таблицу векторов(или вектора прерываний?)? Можно об этом подробнее? Как это делается? Где об этом можно подробнее почитать, а то никакой вразумительной инфы не нашел.

3. Бут и основная программа пишуться ведь как 2 разных проекта?

 

 

Отадельным вопросом - как заставить программу перейти на выполнение с определенного адреса? Где найти об этом информацию?

Пробывал как в примере от ST:

   #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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Я бы делал вариант 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 разных проекта?

Да.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Пробывал как в примере от ST:

Не получилось. На ф-ции Jump_To_Application(); уходит в hardfault

Внимательно проверьте правильно ли и по правильным ли адресам записывается приложение. Я с этим долго бился на LPC17 из-за банальной ошибке при записи данных.

skripach, Ваш пример ничем не отличается от приведённого ТС. Указания нового положения таблицы векторов нет. Или это фича STM? Я правда пока с ними не работал, но предстоит в ближайшее время. Вы ставите указатель на функцию на адрес ResetISR в приложении и обновляете верхушку стека, но не указываете новое расположение таблицы векторов прерываний. Ведь без этого прерывание в приложении приведёт к попутке перехода на вектор в загрузчике.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

skripach, Ваш пример ничем не отличается от приведённого ТС.

Возможно, автор вопрошал "как перейти на выполнение с определенного адреса?" я привел функцию из своего загрузчика под stm32.

Указания нового положения таблицы векторов нет.

Если правильно помню это сделано в приложении, перед прыжком были выключены все прерывания.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Спасибо, что откликнулись.

 

skripach

 

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

 

Вот мой код(для иара, поэтому немного отличается от вашего):

#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 - не было, заменил на функцию подобного содержания из своего хидера. Вот такая функция

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Т.е. по предположению - код должен выполниться в обход строки : GPIOC->BSRR = GPIO_BSRR_BS8; Верно?

Разумеется нет. Функция JumpToApplication это не совсем "перейти на выполнение с определенного адреса", это переход на приложение расположенное по адресу [addr].

Если попытаетесь разобраться с содержимым JumpToApplication то многое станет понятно в том числе почему "матерится на то, что указатель стека...". Также советую посмотреть в дизассамблер.

Кстати, я правильно понимаю алгоритм работы бутлоадера?

После того, как он закончил все свои необходимые действия, нужно:

1. перенести таблицу векторов по адресу в памяти, где начинается основная программа.(т.е. если у меня основная программа записана, начиная с 2й страницы флеша (1 страница - 1кб), то нужно будет сделать так: NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x08000800)

Близко. Таблицу векторов нужно перенести туда где она находится в приложении. Начало приложения и адрес таблицы векторов могут не совпадать, см настройки линкера.

2. Перейти к выполнению кода на тот же адрес (0x08000800)

Нет. Нерейти на начало приложения, в нашем случае адрес начала приложения расположен по адресу (адрес таблицы векторов+4). см. таблицу векторов.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Как у вас все сложно!

Какова структура "обычной" программы под STM32?

В начале идет блок векторов, он состоит из:

-начальное значение стека

-адрес обработчика исключения Reset

-адреса остальных обработчиков ядра

-адреса обработчиков периферии

Далее за ним идет код.

 

Ну так и работать надо с этой структурой, а не с какими-то магическими числами и приведением указателей:

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

 

У меня при сборке приложения линкер сразу за таблицей векторов вписывает размер образа, чтобы загрузчик мог просчитать контрольную сумму загруженного приложения (и только его, не учитывая свободную память).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Спасибо всем за помощь.

 

В целом получилось и перейти на выполнение приложения и перенести таблицу векторов.

 

Вот только если я пытаюсь перенести таблицу в программе бутлоадера - не выходит (не работают прерывания), если же в основной программе - все нормально.

 

Переношу так(т.к. не использую библиотеку для периферии, написал сам) :

 

#define vector_table_offset 0xC00   //смещение
#define offset (uint32_t)(vector_table_offset & 0x1FFFFF80)   //выравнивание

SCB->VTOR =  offset;

 

На приложение перехожу с помощью функции JumpToApplication, которую подсказал skripach.

 

Сергей Борщ, пытался разобрать вашу программу. Не понятна эта строчка:

SCB->VTOR = (uintptr_t)&Application.Vectors;

Возвращается адрес Application.Vectors, после чего явно преобразуется в тип указателя. Тут понятно. А что именно содержит Application.Vectors? И как оно туда попало?

Связано это, видимо, с

От линкера требуется лишь предоставить символ Application с адресом начала той области, в которую загружается приложение.

И на сколько я понимаю, линкер предоставляет структуре адрес начала приложения, потом уже в структуре в соотвествии этому начальному адресу всем векторам присваиваются их адреса?

 

Написана ваша программа, однозначно красивее и четче, единственное, хочется её полностью понять.

А может еще подскажете, где в iar 6.21 линкерный скрипт?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Так же пишу бутлоадер, но для lpc11c24.

Столкнулся с казалось бы элементарной проблемой - не могу прошить даже самую простую программу (моргание светодиодом) по адресу, отличному от нуля.

Т.е. если стартовый адрес во флеш-памяти ставлю 0х00000000, то программа стартует.

Стоит его поменять на, к примеру, 0х00000100 и программа не запускается.

Пишу в Keil. Адрес выставляю следующим образом:

правый клик по проекту-->Options for target-->Linker-->R/O base = 0x00000100.

Попробовал прошить из LPCXpresso, - получил ошибку "vectors still have erased values".

Спасибо за ответы

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Столкнулся с казалось бы элементарной проблемой - не могу прошить даже самую простую программу (моргание светодиодом) по адресу, отличному от нуля.

Прошиваться она может и прошивается, но работать не будет.

 

Пишу в 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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Спасибо Артем, ваш ответ очень помог)

 

По адресу 0х0000 2000 залил "Рабочая программа", которая моргает 4 раза.

 

По адресу 0х0000 0000 тоже залил "Бутлоадер", которая моргает 4 раза, а потом прыгает по адресу 0х2169 (функция main() "Рабочей программы" по MAP-файлу).

 

Все сработало как и ожидалось - МК моргнул 8 раз. Т.е. МК стартовал с "Бутлоадера" затем передал управление "Рабочей программе".

 

Но вот на следующем этапе на место "Рабочей программы" я залил программу по сложнее ( которую собственно говоря и пишу). В итоге МК моргнул 4 раза, а "Рабочая программа" не запустилась. Подозрения падают на то, что она работает по прерываниям, о чем, собственно, ваша ссылка.

 

Не могли вы "на пальцах" объяснить, почему эти прерывания требуют особого отношения и для чего нужен ремап векторов?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Не могли вы "на пальцах" объяснить, почему эти прерывания требуют особого отношения и для чего нужен ремап векторов?

Ремап нужен чтобы и бут и рабочая программа могли использовать прерывания. К тому же вектора расположены по адресам от 4 и получается что приложение хранит адреса своих обработчиков в области бута, что как-то странно (если без ремапа). Если буту прерывания не нужно, то можете посмотреть пример NXP (secondary bootloader). там пример где все обработчики делают jump (или call - не помню точно) в область рабочей программы.

 

 

 

По адресу 0х0000 0000 тоже залил "Бутлоадер", которая моргает 4 раза, а потом прыгает по адресу 0х2169 (функция main() "Рабочей программы" по MAP-файлу).

P.S. Бут должен "прыгать" не на main, а на Reset_Handler.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Исправил на reset handler.

 

По поводу ремапа, как я понял, если "Бутлоадер" залит по адресу 0х0000 0000, а "Рабочая программа" залита по адресу 0х0000 2000 то:

 

"Бутлоадер", перед тем как передать управление "Рабочей программе" должен таблицу векторов, размещенную по адресам 0х0000 0000 - 0х0000 00С0 скопировать в адреса 0х0000 0000 - 0х0000 20С0, иначе "Рабочая программа" не сможет запуститься?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

"Бутлоадер", перед тем как передать управление "Рабочей программе" должен таблицу векторов, размещенную по адресам 0х0000 0000 - 0х0000 00С0 скопировать в адреса 0х0000 0000 - 0х0000 20С0, иначе "Рабочая программа" не сможет запуститься?
Если мы говорим о Cortex-M3 (M4), то нужно записать в регистр VTOR значение 0x00002000, ведь именно по этому адресу начинается таблица векторов вашего приложения (ячека с начальным значением стека также входит в эту таблицу).

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Если мы говорим о Cortex-M3 (M4), то нужно записать в регистр VTOR значение 0x00002000, ведь именно по этому адресу начинается таблица векторов вашего приложения (ячека с начальным значением стека также входит в эту таблицу).

Cortex-M0

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...