Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: STM32 bootloader
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > ARM, 32bit
Страницы: 1, 2, 3
ierofant
Всем привет.

Появилась задача создать бутлоадер, который будет удаленно перепрошивать контроллер. Контроллер работает в связке с 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)

skripach
Я бы делал вариант 2, т.к. в первом варианте во вновь загруженной прошивке может неожиданно вылезти глюк при загрузке новой пошивки и это "конец". sm.gif Да и памяти больше нужно. Хотя конечно глюк может вылезти везде.
Переход на приложение:
Код
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 разных проекта?

Да.
Cosmojam
Цитата(ierofant @ Jan 3 2012, 16:31) *
Пробывал как в примере от ST:
Не получилось. На ф-ции Jump_To_Application(); уходит в hardfault

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

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

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

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, я пока пытаюсь осуществить просто переход выполнения программы по определенному адресу, таблицу векторов даже не трогаю.sm.gif Выше отписался, что у меня не так.


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

Верно ли я все понял?

P.S. Кстати, skripach, спасибо за приведенный код.
skripach
Цитата
Т.е. по предположению - код должен выполниться в обход строки : GPIOC->BSRR = GPIO_BSRR_BS8; Верно?

Разумеется нет. Функция JumpToApplication это не совсем "перейти на выполнение с определенного адреса", это переход на приложение расположенное по адресу [addr].
Если попытаетесь разобраться с содержимым JumpToApplication то многое станет понятно в том числе почему "матерится на то, что указатель стека...". Также советую посмотреть в дизассамблер.
Цитата
Кстати, я правильно понимаю алгоритм работы бутлоадера?
После того, как он закончил все свои необходимые действия, нужно:
1. перенести таблицу векторов по адресу в памяти, где начинается основная программа.(т.е. если у меня основная программа записана, начиная с 2й страницы флеша (1 страница - 1кб), то нужно будет сделать так: NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x08000800)

Близко. Таблицу векторов нужно перенести туда где она находится в приложении. Начало приложения и адрес таблицы векторов могут не совпадать, см настройки линкера.
Цитата
2. Перейти к выполнению кода на тот же адрес (0x08000800)

Нет. Нерейти на начало приложения, в нашем случае адрес начала приложения расположен по адресу (адрес таблицы векторов+4). см. таблицу векторов.
Сергей Борщ
Как у вас все сложно!
Какова структура "обычной" программы под 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

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

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

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

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

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 линкерный скрипт?
Almaz1988
Так же пишу бутлоадер, но для lpc11c24.
Столкнулся с казалось бы элементарной проблемой - не могу прошить даже самую простую программу (моргание светодиодом) по адресу, отличному от нуля.
Т.е. если стартовый адрес во флеш-памяти ставлю 0х00000000, то программа стартует.
Стоит его поменять на, к примеру, 0х00000100 и программа не запускается.
Пишу в Keil. Адрес выставляю следующим образом:
правый клик по проекту-->Options for target-->Linker-->R/O base = 0x00000100.
Попробовал прошить из LPCXpresso, - получил ошибку "vectors still have erased values".
Спасибо за ответы
_Артём_
Цитата(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
Almaz1988
Спасибо Артем, ваш ответ очень помог)

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

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

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

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

Не могли вы "на пальцах" объяснить, почему эти прерывания требуют особого отношения и для чего нужен ремап векторов?
_Артём_
Цитата(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.
Almaz1988
Исправил на reset handler.

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

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

Cortex-M0
Сергей Борщ
QUOTE (Almaz1988 @ Sep 20 2012, 07:24) *
Cortex-M0
Тогда нужно:
1) Располагать (линковать) загрузчик с адреса 0x08000000, чтобы он мог продолжать работать после ремапа.
2) Располагать (линковать) приложение с адреса 0x0800хххх, чтобы онo могло работать после ремапа.
2) Копировать вектора приложения в начало ОЗУ. У приложения должно быть "откушено" начало ОЗУ под это дело в скрипте линкера.
3) Из векторов приложения брать адрес начала стека и прописывать его в MSP
4) Делать ремап (SYSCFG, биты MEM_MODE)
5) передавать управление на адрес, взятый из вектора reset_handler
Almaz1988
Цитата(_Артём_ @ Sep 19 2012, 15:56) *
Если буту прерывания не нужно, то можете посмотреть пример NXP (secondary bootloader)


В этом примере предлагают использовать ассемблерные вставки, добавляю их в main() Бутлоадера:

__asm volatile("ldr r0, =0x103C");
__asm volatile("ldr r0, [r0]");
__asm volatile("mov pc, r0");

Компиллирую проект, выдает ошибку:
aplication\main.c(38): error: #1113: Inline assembler not permitted when generating Thumb code

Добавляю в настройках проекта --arm.
Появляется ошибка:
main.c: Error: C3006E: specified processor or architecture does not support ARM instructions


Цитата(Сергей Борщ @ Sep 20 2012, 10:20) *
Тогда нужно:
1) Располагать (линковать) загрузчик с адреса 0x08000000, чтобы он мог продолжать работать после ремапа.
2) Располагать (линковать) приложение с адреса 0x0800хххх, чтобы онo могло работать после ремапа.
2) Копировать вектора приложения в начало ОЗУ. У приложения должно быть "откушено" начало ОЗУ под это дело в скрипте линкера.
3) Из векторов приложения брать адрес начала стека и прописывать его в MSP
4) Делать ремап (SYSCFG, биты MEM_MODE)
5) передавать управление на адрес, взятый из вектора reset_handler


1,2) Flash-память микроконтроллера lpc11c24 - 0x0000 0000 - 0x0000 8000
RAM-память - 0х1000 0000 - 0х1000 2000
Адреса 0x08000000 у меня нет.
Если загрузчик распологаю не по адресу 0х0000 0000, то у меня МК не стартует.

2) копировать с помощью ассемблерных вставок из NXP примера "secondary bootloader"? Постом ниже я написал о затруднениях, с которыми столкнулся при их использовании.

3,4) также выподняется ассемблерными вставками?




Сергей Борщ
QUOTE (Almaz1988 @ Sep 20 2012, 10:18) *
Компиллирую проект, выдает ошибку:
С кейлом не работаю, не подскажу. Пусть другие участники помогутю
QUOTE (Almaz1988 @ Sep 20 2012, 10:18) *
1,2) Flash-память микроконтроллера lpc11c24
А название ветки - STM32 bootloader. Невнимательно читал ваше первое сообщение и даю советы по STM32F0xx. laughing.gif
Для LPC11 ремапятся первые 512 байт. Соответственно смотрите по ссылке от _Артем_а - там я приводил кусок запуска приложения для LPC11
QUOTE (Almaz1988 @ Sep 20 2012, 10:18) *
2) копировать с помощью ассемблерных вставок из NXP примера "secondary bootloader"? Постом ниже я написал о затруднениях, с которыми столкнулся при их использовании.

3,4) также выподняется ассемблерными вставками?
2 - можно и ассемблером, но зачем? Можно сделать и на С/С++ при помощи цикла и указателей либо библиотечной функцией memcpy()
3) да, ассемблерная вставка либо функция из CMSIS, которая тоже на ассемблерной вставке строится.
4) Обычная сишная запись в регистр. Для LPC это будет регистр SYSMEMREMAP
Almaz1988
2) а если размещать вектор прерываний не в RAM, а во flash по адресу 0х0000 2000 (сюда у меня рабочая программа линкуется), с помощью встроенных IAP-команд, которые позволяют записывать во флеш страницами по 256 байт?
Нужно ли будет в таком случае делать Remap?
3) Не помните названия функции?
Сергей Борщ
QUOTE (Almaz1988 @ Sep 20 2012, 14:01) *
2) а если размещать вектор прерываний не в RAM, а во flash по адресу 0х0000 2000
Разместить вектора вы можете где угодно, но вот процессор читает их с адреса 0x00000000. И никаких способов считывать их из других адресов у Cortex-M0 не предусмотрено (у M3 для этого есть регистр VTOR). Поэтому разработчики процессоров идут на хитрость - делают отражение (remap) на эти адреса других регионов памяти, например RAM. После ремапа вы кладете какие-то данные в начало RAM, а процессор их "видит" не только по "родным" адресам в RAM, но и в начале флеша. И таких мест, которые могут быть отражены на начало адресного пространства всего два - начало ОЗУ и начало ПЗУ со встроенным загрузчиком (ISP). Эти места прибиты гвоздями к своим адресам разработчиками процессора.
QUOTE (Almaz1988 @ Sep 20 2012, 14:01) *
3) Не помните названия функции?
Нет, я не использую CMSIS (только заголовочный файл с описанием адресов регистров). Поищите поиском по файлам, ключевое слово "MSP" вы уже знаете. Или обратитесь в техподдержку Кейла -они должны быстро отвечать на вопросы покупателей своего продукта.
Almaz1988
Т.е., когда я заливаю "Загрузчик" по адресу 0х0000 0000 и "Рабочую программу" по адресу 0х0000 2000 обе программы обращаются к одной и той же таблице векторов, которая расположена по адресу 0х0000 0000. Из-за этого "Рабочая программа" запуститься не может, потому что в ней находятся данные "Загрузчика".
Я правильно понял суть проблемы?
Сергей Борщ
Примерно так. Процессор умеет читать вектора только из одного места. Чтобы и приложение и загрузчик оба могли использовать прерывания надо на время работы приложения подсунуть в это место таблицу векторов приложения.
Almaz1988
А как такое решение проблемы:
Проект №1 - "Загрузчик"
Проект №2 - "Рабочая программа"

После того как мы их скомпилировали, открыть HEX-файлы обоих проектов.
В ручную заменить строки в НЕХ-файле "Загрузчика", соответствующие таблице векторов прерываний на аналогичные строки из НЕХ-файла "Рабочей программы" не трогая только первые две ячейки: main stack pointer и reset vector.

И поскольку в моем "Загрузчике" не используются прерывания, то передав управление "Рабочей программе" та запустится.

Сработает?

А как такое решение проблемы:
Проект №1 - "Загрузчик"
Проект №2 - "Рабочая программа"

После того как мы их скомпилировали, открыть HEX-файлы обоих проектов.
В ручную заменить строки в НЕХ-файле "Загрузчика", соответствующие таблице векторов прерываний на аналогичные строки из НЕХ-файла "Рабочей программы" не трогая только первые две ячейки: main stack pointer и reset vector.

И поскольку в моем "Загрузчике" не используются прерывания, то передав управление "Рабочей программе" та запустится.

Сработает?
Alex19
Если Вы в загрузчике не используете прерывания - сделайте как в примере от NXP (secondary bootloader)
В прерываниях загрузчика сделайте редирект на прерывания приложения. Как-то так можно:
(для загрузчика размером 4кб(0x1000) )

#define BOOTLOADER_SIZE 0x1000

#define redirect(address) unsigned long pc = *(unsigned long*)(address) + (BOOTLOADER_SIZE); void (*redirect_handler)(void) = (void(*)(void))pc;\ redirect_handler();

....
void CT16B0_IRQHandler(void) { redirect(0x1080); }
void CT16B1_IRQHandler(void) { redirect(0x1084); }
void CT32B0_IRQHandler(void) { redirect(0x1088); }
.....
Сергей Борщ
QUOTE (Almaz1988 @ Sep 21 2012, 09:37) *
После того как мы их скомпилировали, открыть HEX-файлы обоих проектов.
В ручную заменить строки в НЕХ-файле "Загрузчика", соответствующие таблице векторов прерываний на аналогичные строки из НЕХ-файла "Рабочей программы" не трогая только первые две ячейки: main stack pointer и reset vector.
Давайте думать дальше. Вы доработали рабочую программу и ее обработчики прерываний оказались по другим адресам. Вам снова надо брать обе прошивики, снова копировать строки HEX-файлов, программировать уже обновленный загрузчик. Тогда какой в нем смысл, если его надо править и заливать программатором перед каждым обновлением приложения?
В чем проблема? Вам жалко 256 байт ОЗУ (а реально меньше, ибо не вся таблица используется)?
Almaz1988
Цитата(Сергей Борщ @ Sep 21 2012, 11:51) *
Давайте думать дальше. Вы доработали рабочую программу и ее обработчики прерываний оказались по другим адресам. Вам снова надо брать обе прошивики, снова копировать строки HEX-файлов, программировать уже обновленный загрузчик. Тогда какой в нем смысл, если его надо править и заливать программатором перед каждым обновлением приложения?
В чем проблема? Вам жалко 256 байт ОЗУ (а реально меньше, ибо не вся таблица используется)?

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

1)Самое простое в ручную из одного HEX-файла скопировать данные по адресам 0х09-0хС0 (по этим адресам располагается таблица векторов. Не трогаю лишь два первых вектора 0х00 - 0х08). Получилось "Рабочая программа" запустилась.

2) Перешел к более сложному - копирую файлы 0х00 - 0х08 (MSP и Reset handler) программно с помощью чего осуществляю прыжок в "Рабочую программу" и она запускается, если не использует прерываний:

#include "LPC11xx.h"
#include "core_cm0.h"
#include "system_LPC11xx.h"

__ASM void __jump_( )
{
ldr r0, =0x1000
ldr r0, [r0]
mov sp, r0

ldr r0, =0x1004
ldr r0, [r0]
mov pc, r0
}

nt main(void)
{
SystemInit();
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);

__jump_( );

while(1);
}

3)Перехожу к более сложному - программно копирую всю таблицу прерываний - 0х00 - 0хС0.
Текст программы:

#include "LPC11xx.h"
#include "core_cm0.h"
#include "system_LPC11xx.h"

__ASM void __jump_( )
{
ldr r0, =0x1000
ldr r0, [r0]
mov sp, r0

ldr r0, =0x1004
ldr r0, [r0]
mov pc, r0

ldr r0, =0x1008
ldr r0, [r0]
ldr r1, =0x0008
mov [r1], r0

ldr r0, =0x1008
ldr r0, [r0]
ldr r1, =0x0008
mov [r1], r0

......................
//здесь такие же наборы команд для других адресов
......................

ldr r0, =0x10BC
ldr r0, [r0]
ldr r1, =0x00BC
mov [r1], r0


}

int main(void)
{
SystemInit();
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);

__jump_( );

while(1);
}

И тут сталкиваюсь с затруднением - компилятор на строке "mov [r1], r0" выдает ошибку:
error: A1647E: Bad register name symbol, expected Integer register
В чем дело?
Эта команда же допускает копирование из РОН в память




Сергей Борщ
QUOTE (Almaz1988 @ Sep 21 2012, 13:00) *
Эта команда же допускает копирование из РОН в память
Если вы читаете из памяти командной ldr, то писать в нее логично командой str.
Но, мама дорогая! Почему на ассемблере, да еще и тупым copy-paste? Это, кажется, называется "индусский код"?

CODE
uint32_t const * pSrc = (uint32_t const *)0x1000;
uint32_t * pDst = (uint32_t const *)0x0000;
#define VECTORS_COUNT  64   // подставьте сколько нужно, включая указатель стека и reset handler
for(uint_fast8_t i = 0; i < VECTORS_COUNT; ++i)
   *pDst++ = *pSrc++;

Almaz1988
Цитата(Сергей Борщ @ Sep 21 2012, 14:12) *
Если вы читаете из памяти командной ldr, то писать в нее логично командой str.
Но, мама дорогая! Почему на ассемблере, да еще и тупым copy-paste? Это, кажется, называется "индусский код"?

Код
uint32_t const * pSrc = (uint32_t const *)0x1000;
uint32_t * pDst = (uint32_t const *)0x0000;
#define VECTORS_COUNT  64   // подставьте сколько нужно, включая указатель стека и reset handler
for(uint_fast8_t i = 0; i < VECTORS_COUNT; ++i)
   *pDst++ = *pSrc++;


Индусский ли пиндосский ли...))

Ваш кусок кода не перепрыгивает в "Рабочую программу". Видимо на STM32 есть возможность напрямую писать во флеш (в lpc11xx это возможно только посредством специальных IAP команд)
Написанная мною тоже не пашет:

ldr r0, =0x1004 ; загружаем в r0 константу 0х1004
ldr r0, [r0] ; загружаем в r0 содержимое по адресу 0х1004
ldr r1, =0x0004 ; загружаем в r0 константу 0х0004
str r0, [r1] ; загружаем значение r0 в адрес r1

Сергей Борщ
QUOTE (Almaz1988 @ Sep 21 2012, 15:30) *
Видимо на STM32 есть возможность напрямую писать во флеш
Вы и себя запутали и я просто перенес на С ваш код с некоторой оптимизацией. Не нужно писать во флеш. И не нуждно копировать в адрес 0. Надо копировать в начало ОЗУ. И потом ремапом подставлять эту область ОЗУ на нулевые адреса:

CODE
uint32_t const * pSrc = (uint32_t const *)0x00001000;  // начало приложения
uint32_t * pDst = (uint32_t *)0x10000000;                 // начало ОЗУ
#define VECTORS_COUNT  64   // подставьте сколько нужно, включая указатель стека и reset handler
for(uint_fast8_t i = 0; i < VECTORS_COUNT; ++i)
   *pDst++ = *pSrc++;
// далее надо загрузить указатель стека.
не_знаю_как_это_сделать_в_кейле(*(uint32_t const *)0x00001000);
//
LPC_SYSCON->SYSMEMREMAP = 1;  // remap to ram
void (*Application)();
Application = *(void (**)())0x10000004;
Application();
Almaz1988
Цитата(Сергей Борщ @ Sep 21 2012, 16:45) *
Вы и себя запутали и я просто перенес на С ваш код с некоторой оптимизацией. Не нужно писать во флеш. И не нуждно копировать в адрес 0. Надо копировать в начало ОЗУ. И потом ремапом подставлять эту область ОЗУ на нулевые адреса:

Код
uint32_t const * pSrc = (uint32_t const *)0x00001000;  // начало приложения
uint32_t * pDst = (uint32_t *)0x10000000;                 // начало ОЗУ
#define VECTORS_COUNT  64   // подставьте сколько нужно, включая указатель стека и reset handler
for(uint_fast8_t i = 0; i < VECTORS_COUNT; ++i)
   *pDst++ = *pSrc++;
// далее надо загрузить указатель стека.
не_знаю_как_это_сделать_в_кейле(*(uint32_t const *)0x00001000);
//
LPC_SYSCON->SYSMEMREMAP = 1;  // remap to ram
void (*Application)();
Application = *(void (**)())0x10000004;
Application();


Спасибо за пояснения)) Но с ремапом у меня нивкакую запускаться не хочет))
Продолжу с понедельника.
П.с. и все же есть возможность записи во флеш ассемблерными вставками?(Как запасной вариант, если ремап не запустится)
Сергей Борщ
Возможность записи во флеш есть. Ведь как-то этот bootloader должен записать во флеш вашу основную программу? И делается это вызовом функций встроенного заводского загрузчика (IAP). Что писать и куда писать - ему все равно. И на каком языке вы напишете вызов его функций - ему тоже все равно. Почему с REMAP ом не получится - непонятно. У всех получается. Посмотрите внимательно, на какие адреса попадает ваша команда ремапа. Я обращал внимание Артема в той ветке на этот момет - в момент ремапа содержимое первых 512 байтов флеш "накрывается" содержимым ОЗУ. И если следующая за ремапом команда вашего загрузчика попала в эту область - вместо нее будет выполнен мусор из соответствующих адресов ОЗУ. Поэтому если ваш загрузчик настолько мал, что команда находится в первых 512 байтах флеша - надо как-то ее оттуда отодвинуть. Либо поколдовать со скриптом линкера, либо добавить кода.
Almaz1988
А IAP-команды переводятся же в ассемблерный код при компилляции? Это ведь не отдельный язык программирования.
toweroff
Цитата(Almaz1988 @ Sep 21 2012, 18:57) *
А IAP-команды переводятся же в ассемблерный код при компилляции? Это ведь не отдельный язык программирования.

нет, конечно

в разные регистры заносятся команда, номер начального сектора, конечного сектора, адрес блока данных
вызывается IAP, адрес которого заранее известен
из еще одного регистра читается результат операции

какая разница, каким языком это дело описать?

а вот требования к выполнению IAP (как то - отключить PLL, например, или вызов IAP не из области FLASH) нужно выполнять обязательно
_Артём_
Цитата(toweroff @ Sep 21 2012, 19:10) *
а вот требования к выполнению IAP (как то - отключить PLL, например

В UM10398 Chapter 26: LPC111x/LPC11Cxx Flash programming firmware PLL не упоминается (или я не нашёл). Ссылку приведёте?


Цитата(toweroff @ Sep 21 2012, 19:10) *
вызов IAP не из области FLASH

В примере от NXP функции IAP вызываются из flash. Наверняка потому, что из bootloader не использует прерываний. В случае испоользования прерываний таблицу векторов и обработчики нужно поместить в RAM или запрещать прерывания на время стирания/записи flash.
toweroff
Цитата(_Артём_ @ Sep 22 2012, 16:13) *
Ссылку приведёте?


Цитата(toweroff)
(как то - отключить PLL, например,


где-то это требуется, где-то - нет. Здесь весь смысл в том, что требования выполнения IAP, если они есть, выполнять нужно, а вот на каком языке это делать - никакой принципиальной разницы нет
_Артём_
Цитата(toweroff @ Sep 22 2012, 19:05) *
Здесь весь смысл в том, что требования выполнения IAP, если они есть, выполнять нужно,

Ну, если так...
Но всё равно спасибо - просмотрел ещё раз UM и обнаружил требование запрета прерываний на момент записи .
Хотя работало и без запрета - может функции IAP запрещают прерывания, если таблица не в ОЗУ?
AHTOXA
Кхм...
Господа, а как обсуждение IAP от NXP относится к теме "STM32 bootloader"? sm.gif
Может быть, выделить это обсуждение в отдельную тему? А то путаница получается.
toweroff
Цитата(AHTOXA @ Sep 23 2012, 00:02) *
Господа, а как обсуждение IAP от NXP относится к теме "STM32 bootloader"? sm.gif

да все как обычно biggrin.gif
Almaz1988
#include "LPC11xx.h"
#include "rom_drivers.h"
#include "gpio.h"
#include "string.h"
#include "type.h"
#include "core_cm0.h"
#include "system_LPC11xx.h"
#include "application_Flash.h"


__ASM void __copy_MSP_( )
{
ldr r0, =0x10000000
ldr r0, [r0]
mov sp, r0
}

__ASM void __copy_reset_handler_( )
{
ldr r0, =0x10000004
ldr r0, [r0]
bx r0
}

/********************************************************** Main function ********************************/
int main(void)
{
SystemInit();

LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);

uint32_t const * pSrc = (uint32_t const *)0x00001000; //копируем первые 200 байт флеша, начиная с адреса 0х1000 в ОЗУ
uint32_t * pDst = (uint32_t *)0x10000000;
#define VECTORS_COUNT 128
for(uint_fast8_t i = 0; i < VECTORS_COUNT; ++i)
*pDst++ = *pSrc++;

__copy_MSP_( ); //загружаем в стек

__disable_irq(); //запрещаем прерывания
LPC_SYSCON->SYSMEMREMAP = 0x01; // remap to ram

__copy_reset_handler_(); //прыгаем на Resrt_handler

while(1);
}


Работает только если в "рабочей программе" не используются прерывания. Если прерывания используются, то "Рабоча программа" нивкакую не стартует.
AndreFF
Здравствуйте!
Просьба откликнуться кто в теме. Пишу загрузчик для своего приложения (IAR 6.40, STM32F103VET). Как можно в процессе линковки вставить размер образа по конкретному адресу? Это точно можно сделать, тому пример пост Сергея Борща :

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

MK2
тоже интересовал этот вопрос, но решил следующим образом: зафиксировал длину прошивки контроллера, просто принимаю за максимальный объем флешки контроллера (вернее то что остается от бутлоадера)
Сергей Борщ
QUOTE (AndreFF @ Mar 2 2013, 09:01) *
Это точно можно сделать, тому пример пост Сергея Борща :
Я использую gcc. Про ИАР ничего не скажу. Должна быть какая-то директива размещения константы, надо читать описание линкера.
AndreFF
Спасибо за ответы.
Пробовал следовать советам официальной техподдержки IAR
http://supp.iar.com/Support/?note=62709&from=note+65473
а именно пункта Alternative solution using checksum-start and checksum-end markers
Всё отлично, есть константы с адресами начала и конца проекта, считается CRC32, только в Нех файл значение CRC не заносится категорически, вместо него там нули. А в дизассемблере нормальное значение. Пока не победил это.
vovanxp
Хочу сделать загрузку прошивки через веб, но неполучается, не запускается основная программа.

Делаю на основе проекта с STM32CubeF4, LwIP_IAP. Манеул к этому проекту
http://www.st.com/st-web-ui/static/active/.../DM00103145.pdf

Среда Keil uVision, мк STM32f4
------------------------------------------

В бутлоадере выставил начальный адрес с которого будет стартовать основная программа
#define USER_FLASH_FIRST_PAGE_ADDRESS 0x08020000


В основной программе изменил начальный адресс 0x08020000
В основной программе изменил смещение для таблици векторов #define VECT_TAB_OFFSET 0x20000
------------------------------------------
После загрузки прошивок, доходит до Jump_To_Application(); и все, дальше тишина.
Переменная JumpAddress = 0x080201A5

if (((*(__IO uint32_t*)USER_FLASH_FIRST_PAGE_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
{
JumpAddress = *(__IO uint32_t*) (USER_FLASH_FIRST_PAGE_ADDRESS + 4);
Jump_To_Application = (pFunction) JumpAddress;
__set_MSP(*(__IO uint32_t*) USER_FLASH_FIRST_PAGE_ADDRESS);
Jump_To_Application();
}

Основная программа начинается с адреса 0x08020000 смотрел через STM32 ST-LINK Utility
На скринах без смещения с со смещением, данные совпадаю, отличие только адресами.

В чем может быть причина?
Спасибо.
Kabdim
Цитата(vovanxp @ Dec 11 2014, 11:55) *
В чем может быть причина?
Спасибо.

Выглядит более менее, за исключением того что неясно как вы меняете таблицу прерываний с бутлоадера на вашу программу. Вы блинк пробовали прошить?
vovanxp
Цитата(Kabdim @ Dec 12 2014, 14:46) *
Выглядит более менее, за исключением того что неясно как вы меняете таблицу прерываний с бутлоадера на вашу программу.


А где менять менять? Я с таблицей ничего не делал.
Я думал что таблица прерываний по умолчанию в основной программе будет после адреса 0x08020000

Цитата(Kabdim @ Dec 12 2014, 14:46) *
Вы блинк пробовали прошить?

Я свою прошивку заливаю.


Kabdim
Цитата(vovanxp @ Dec 12 2014, 14:12) *
А где менять менять? Я с таблицей ничего не делал.
Я думал что таблица прерываний по умолчанию в основной программе будет после адреса 0x08020000


Я свою прошивку заливаю.

SCB->VTOR - на этом форуме очень много тем посвященных бутлоадерам.
Залейте простейшую мигалку что бы понять правильно ли вы слинковались и заливаете. Заодно если она заработает, значит дело действительно в таблице прерываний.
Еще стоит отключать прерывания во время смены таблицы и перехода т.к. они могут испортить процесс.

Вдогонку, если у вас thumb, то младший бит адреса по которому переходите должен быть 1.
vovanxp
Цитата(Kabdim @ Dec 12 2014, 16:19) *
SCB->VTOR - на этом форуме очень много тем посвященных бутлоадерам.
Залейте простейшую мигалку что бы понять правильно ли вы слинковались и заливаете. Заодно если она заработает, значит дело действительно в таблице прерываний.
Еще стоит отключать прерывания во время смены таблицы и перехода т.к. они могут испортить процесс.

Вдогонку, если у вас thumb, то младший бит адреса по которому переходите должен быть 1.


В моей программе(не в загрузчике) я поменял дефайн
//#define VECT_TAB_OFFSET 0x00
#define VECT_TAB_OFFSET 0x20000

В SystemInit() есть такой кусок кода

#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
Kabdim
Цитата(vovanxp @ Dec 12 2014, 17:56) *
В моей программе(не в загрузчике) я поменял дефайн

Видимо нужно отладится в асемблере. К примеру мой код для кортекса м0
Код
void JumpToMainProgram() {
    __disable_irq();
    memcpy(MEMORY_OFFSET, MAIN_PROG_FLASH_OFFSET, INT_MEM_TO_MAP_SIZE);
    __set_MSP(*MAIN_PROG_FLASH_OFFSET);
    LPC_SYSCON->SYSMEMREMAP = 1;
    __enable_irq();
    ISRPtr application_reset_handler = *((ISRPtr*) (MAIN_PROG_FLASH_OFFSET + 1));
    goto *application_reset_handler;

}


MAIN_PROG_FLASH_OFFSET - указатель на u32
vovanxp
Только что удалось прошить через веб, причина было совсем капец, инициализацию IWDG закоментировал, а в обработчике прерывания таймера оставил HAL_IWDG_Refresh(&Hiwdg);
При обычном режиме все работало, но если основная программа начиналась с 0x8020000 программа не работала.

Одним словом причина была совсем в ином.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.