Микроконтроллер STM32 и Bootloader. Пример реализации.

Создание bootloader

Продолжаем обсуждать bootloader на нашем сайте и сегодня, как я и обещал, создадим свой собственный загрузчик и попробуем его в деле. Все теоретические аспекты мы рассмотрели в предыдущей статье (ссылка), так что сейчас только практика и ничего кроме практики!

Итак, для начала разберемся с постановкой задачи. В качестве электронной части будет выступать отладочная плата MiniSTM32, соответственно, проект будем создавать для семейства микроконтроллеров STM32F10x. Наш bootloader будет работать с SD-картой, то есть при обнаружении файла прошивки на подключенной к плате карте, загрузчик должен будем выполнить перепрограммирование микроконтроллера. В качестве среды разработки я в этот раз буду использовать IAR (у меня – IAR v.6.70). И, конечно же, в конце статьи я обязательно выложу полные проекты!

Со вступлением закончили, переходим сразу к коду 🙂

Для начала определим нужные нам адреса, а также параметры Flash-памяти. В моем контроллере памяти 512 кБ – 256 страниц по 2 кБ. Кроме того, мы выделим отдельную страницу для хранения специального ключа-флага. Зачем он нужен? А для того, чтобы сохранить информацию при перезапуске контроллера. Для перехода по адресу основной программы нам нужно сбросить всю периферию в начальное состояние, и это проще всего сделать просто перезапустив микроконтроллер.

Таким образом, если bootloader обнаружил прошивку и запрограммировал контроллер, он запишет в специальное место во Flash произвольное, заранее выбранное значение и перезапустит контроллер. При входе в функцию main() после ресета контроллер считает значение “ключа” из Flash-памяти, и если там записано нужное значение, то значит программа была прошита и можно выполнять переход, а если этого значения там нет, то значит перепрограммирования не было и bootloader продолжит сканировать SD-карту. Проще говоря, при включении микроконтроллер будет считывать значение из памяти и если оно совпадет с ключом, то произойдет переход на адрес основной программы, а если нет, то bootloader продолжит сканировать карту.

В нашем примере в качестве ключа будет выступать значение 0xAAAA5555 и храниться ключ будет в 20-ой странице Flash-памяти:

// Bootloader key configuration
#define BOOTLOADER_KEY_START_ADDRESS                             (uint32_t)0x08009800
#define BOOTLOADER_KEY_PAGE_NUMBER                               19
#define BOOTLOADER_KEY_VALUE                                     0xAAAA5555

// Flash configuration
#define MAIN_PROGRAM_START_ADDRESS                               (uint32_t)0x0800A000
#define MAIN_PROGRAM_PAGE_NUMBER                                 20
#define NUM_OF_PAGES                                             256
#define FLASH_PAGE_SIZE                                          2048

Основная же программа, как видите, будет прошиваться начиная с 20-ой страницы Flash. Теперь определим нужные нам функции для сохранения и чтения ключа:

/***************************************************************************************/
// Function                SetKey()
// Description             Sets bootloader key
// Parameters              None
// RetVal                  None
/***************************************************************************************/
void SetKey()
{
	FLASH_Unlock();
	FLASH_ProgramWord(BOOTLOADER_KEY_START_ADDRESS, BOOTLOADER_KEY_VALUE);
	FLASH_Lock();
} // End of SetKey()


/***************************************************************************************/
// Function                ResetKey()
// Description             Resets bootloader key
// Parameters              None
// RetVal                  None
/***************************************************************************************/
void ResetKey()
{
	FLASH_Unlock();
	FLASH_ErasePage(BOOTLOADER_KEY_START_ADDRESS);
	FLASH_Lock();
} // End of ResetKey()


/***************************************************************************************/
// Function                ReadKey()
// Description             Reads bootloader key value
// Parameters              None
// RetVal                  None
/***************************************************************************************/
uint32_t ReadKey()
{
	return (*(__IO uint32_t*) BOOTLOADER_KEY_START_ADDRESS);
} // End of ReadKey()


/***************************************************************************************/

Готово! Без лишних остановок двигаемся дальше… И на очереди функция main(). В самом начале проверяем ключ и если он совпал совершаем переход:

if (ReadKey() == BOOTLOADER_KEY_VALUE)
{
	ResetKey();
	__disable_irq();
	NVIC_SetVectorTable(NVIC_VectTab_FLASH, MAIN_PROGRAM_START_ADDRESS);

	jumpAddress = *(__IO uint32_t*) (MAIN_PROGRAM_START_ADDRESS + 4);
	Jump_To_Application = (pFunction) jumpAddress;
	__set_MSP(*(__IO uint32_t*) MAIN_PROGRAM_START_ADDRESS);
	Jump_To_Application();
}

Переход осуществляется следующим образом. Первым делом сбрасываем ключ и выключаем прерывания. Затем переносим таблицу векторов прерывания на адреса, соответствующие основной программе. А непосредственно переход заключается в последующих четырех строках. Важным моментом является то, что мы берем адрес (MAIN_PROGRAM_START_ADDRESS + 4), это связано с тем, что по адресу MAIN_PROGRAM_START_ADDRESS сначала записывается таблица векторов прерываний.

Идем дальше и тут начинается самое интересное – если ключ не совпал, то мы начинаем проверять SD-карту на наличие файла прошивки. И для начала инициализируем интерфейс SDIO:

NVIC_InitTypeDef NVIC_InitStructure;
SD_Init();

// SDIO Interrupt ENABLE 
NVIC_InitStructure.NVIC_IRQChannel = SDIO_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

// DMA2 Channel4 Interrupt ENABLE 
NVIC_InitStructure.NVIC_IRQChannel = DMA2_Channel4_5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_Init(&NVIC_InitStructure);
SD_GetCardInfo(&SDCard);
SD_SelectDeselect((uint32_t)(SDCard.RCA << 16));

Работу с картами памяти мы уже много раз обсуждали – например тут, поэтому на этом не будет подробно останавливаться. А дальше мы проверяем, есть ли на карте файл прошивки (зададим для него имя – program.bin):

FATFS SDfs;
FIL program;

if(f_mount(&SDfs, "0", 1) == FR_OK)
{
	uint8_t path[12] = "program.bin";
	path[11] = '
FATFS SDfs;
FIL program;
if(f_mount(&SDfs, "0", 1) == FR_OK)
{
uint8_t path[12] = "program.bin";
path[11] = '\0';
result = f_open(&program, (char*)path, FA_READ);
if (result == FR_OK)
{
// Program........
'; result = f_open(&program, (char*)path, FA_READ); if (result == FR_OK) { // Program........

И если нужный нам файл найден, то мы должны произвести программирование Flash-памяти. Реализуем это! Для начала разблокируем Flash-память:

FLASH_Unlock();

Теперь нам необходимо произвести очистку всех страниц памяти, соответствующих основной программе. В нашем случае это страницы с 20-ой до последней, 256-ой:

for(uint8_t i = 0; i < (NUM_OF_PAGES - MAIN_PROGRAM_PAGE_NUMBER); i++)
{
	FLASH_ErasePage(MAIN_PROGRAM_START_ADDRESS + i * FLASH_PAGE_SIZE);
}

Само программирование мы будем осуществлять порциями по 512 байт, то есть считываем из файла 512 байт, записываем их во Flash, затем считываем следующие 512 байт и так далее… При этом будем сохранять в переменной количество уже записанных байт и сравнивать это значение с общим числом байт. Если осталось записать больше 512 байт, то считываем их из файла и записываем. Как только нам останется записать меньше 512 байт, мы считываем оставшееся количество и записываем во Flash эти последние байты. В программе это выглядит следующим образом:

programBytesToRead = program.fsize;
programBytesCounter = 0;
currentAddress = MAIN_PROGRAM_START_ADDRESS;

while ((programBytesToRead -  programBytesCounter) >= 512)
{
	f_read(&program, readBuffer, 512, &readBytes);
	programBytesCounter += 512;

	for (uint32_t i = 0; i < 512; i += 4)
	{
		FLASH_ProgramWord(currentAddress, *(uint32_t*)&readBuffer[i]);
		currentAddress += 4;
	}
}

if (programBytesToRead != programBytesCounter)
{
	f_read(&program, readBuffer, (programBytesToRead - programBytesCounter), &readBytes);

	for (uint32_t i = 0; i < (programBytesToRead - programBytesCounter); i += 4)
	{
		FLASH_ProgramWord(currentAddress, *(uint32_t*)&readBuffer[i]);
		currentAddress += 4;
	}
	
	programBytesCounter = programBytesToRead;
}

И после окончания этих операций не забываем заблокировать Flash и закрыть файл:

FLASH_Lock();
f_close(&program);

Кроме того удаляем файл прошивки с SD-карты, ведь иначе при каждом перезапуске STM32 bootloader будет находить один и тот же файл и раз за разом записывать его во Flash-память:

f_unlink((char*)path);

Отключаем прерывания, сохраняем ключ и выполняем перезапуск контроллера:

NVIC_DisableIRQ(DMA2_Channel4_5_IRQn);
NVIC_DisableIRQ(SDIO_IRQn);
ResetKey();
SetKey();
NVIC_SystemReset();

На этом с программированием памяти вроде бы все. А что же делать, если файл не обнаружен? В этом случае мы анализируем байты, записанные по адресу основной программы и если они сигнализируют нам о том, что основная программа уже находится во Flash (то есть была туда помещена ранее), то мы делаем вывод, что она не нуждается в обновлении (ведь файл прошивки отсутствует), записываем ключ и выполняем перезапуск. А контроллер при включении, увидев ключ, просто перейдет на основную программу.

Если же основная программа отсутствует в памяти, то мы также выполняем перезапуск, но не записываем при этом ключ. Тогда контроллер, перезапустившись, начнет заново сканировать карту-памяти на наличие файла прошивки:

if (((*(uint32_t*)MAIN_PROGRAM_START_ADDRESS) & 0x2FFF0000 ) == 0x20000000)
{
	ResetKey();
	SetKey();
	NVIC_SystemReset();
}
else
{
	NVIC_SystemReset();
}

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

Итак, создаем абсолютно новый проект и код там будет достаточно простой – инициализируем таймер и вывод, на котором находится светодиод на плате. И в прерывании по таймеру моргаем диодом. Отличие от обычных проектов тут будет в том, что в начале функции main() мы вызываем функцию для переноса таблицы векторов прерываний в нужное нам место:

int main()
{
	__set_PRIMASK(1);
	NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0800A000);
	__set_PRIMASK(0);

	__enable_irq();

	initAll();

	TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
	TIM_Cmd(TIM4, ENABLE);
	NVIC_EnableIRQ(TIM4_IRQn);

	while(1)
	{
	}

	return 0;
}


void TIM4_IRQHandler()
{
	TIM_ClearITPendingBit(TIM4, TIM_IT_Update);

	if (currentState == 0)
	{
		currentState = 1;
		GPIO_SetBits(GPIOC, GPIO_Pin_6);
	}
	else
	{
		currentState = 0;
		GPIO_ResetBits(GPIOC, GPIO_Pin_6);
	}
}

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

На самом деле и это еще не все, наш проект мы должны еще правильным образом настроить! И для этого нам надо зайти в свойства проекта во вкладку Linker. Там, нажав на кнопку Edit мы должны прописать адреса, соответствующие нашей основной программе, то есть в данном случае адрес 0x0800A000:

Bootloader и STM32

Также во вкладке Memory Regions в поле ROM Start надо указать тот же адрес – 0x0800A000.

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

Если мы будем использовать стандартный файл, входящий в состав IAR и поменяем в нем адреса для проекта с bootloader’ом, то затем создав новый проект для такого же микроконтроллера, в котором bootloader не используется или используются другие адреса, мы получим ошибки, ведь файл линкера то один и тот же и он уже изменен нами ранее совсем для другого проекта. Поэтому для всех проектов для STM32 с bootloader’ом я копирую стандартный файл линкера к себе в папку с проектом и уже в него спокойно записываю новые адреса, поскольку этот файл будет использоваться только для этого проекта.

И вот на этом мы на сегодня заканчиваем, надеюсь статья получилась полезной и понятной 🙂 Как и обещал, выкладываю два полных проекта:

До скорых встреч на нашем сайте!

Поделиться!

Подписаться
Уведомление о
guest
138 Комментарий
старее
новее большинство голосов
Inline Feedbacks
View all comments
Sergey
Sergey
5 лет назад

Попробовал для stm32f407.
Работает, только маленький нюанс (убил полдня), перенос векторов прерывания происходит в system_stm32f4xx.с в функции SystemInit, , достаточно там изменить VECT_TAB_OFFSET, которое по умолчанию 0

Sergey
Sergey
5 лет назад

Еще есть не понятка:
зачем до и после перехода на осн. программу переносить вектора?
У меня только в основной программе.

Ars
Ars
5 лет назад

Здравствуйте Aveal, начал изучать STM32 ваша информация на мой взгляд лучшая в рунете, примеры кода мало где я находил, есть куча вопросов, куда их можно задать и можно ли с вами общаться помимо комментариев?

Westbam
Westbam
5 лет назад

Спасибо за интересную статью.
С обновлением прошивки через bootloader ясно, а
вот как быть если нам требуется обновить сам загрузчик?

Westbam
Westbam
5 лет назад

Я не этот вариант имел ввиду.
Меня интересует возможность обновления загрузчика, без программатора, например по usb. У меня есть устройство которое умеет так делать, производитель выпускает две прошивки(они зашифрованы): сам загрузчик и основная прошивка. У меня есть мысль что сам загрузчик в таком варианте грузиться из RAM и от туда сам себя обновляет.

Westbam
Westbam
5 лет назад

Выходит для того что бы загрузчик мог перезаписать сам себя,
надо иметь два bootloader’a, либо грузить его из RAM?
Меня интересуют как такое реализуют просто.

Sergey
Sergey
5 лет назад

Да почему. Нет.
Можно обновлять загрузчик так:
В основной программе проверять версию загрузчика (хотя бы по контрольной сумме) он же всегда в 0 секторе.
Если старый то просто стереть загрузчик (он же во время работы основной программы “мертв”-не работает и не как не влияет) и записать новый, который хронится в основной программе как данные(можно и сложнее считывать например с SD).
Надеюсь мысль понятна…

Dmitry R
Dmitry R
5 лет назад

“Важным моментом является то, что мы берем адрес (MAIN_PROGRAM_START_ADDRESS + 4), это связано с тем, что по адресу MAIN_PROGRAM_START_ADDRESS сначала записывается таблица векторов прерываний.”

Тут вы вводите в заблуждение : создается впечатление что таблица занимает 4 байта, а потом якобы стартует main(основная программа). На самом деле со смещением в 4 байта от начала таблицы находится вектор на Reset_Handler. С него то и начинается выполнение кода (переход на SystemInit). (на нулевом адресе инициализируется Stack Pointer)

Dmitry R
Dmitry R
5 лет назад

Реализуют это как вы правильно догадались запуском загрузчика из RAM. Загрузчик находится в основной прошивке, и если нужно программа загружает его в RAM, потом передает ему управление и он обновляет flash. При этом нет никаких танцев с настройками проекта и переносом таблицы прерываний. Ну и загрузчик обновляется вместе с прошивкой. Такой способ предпочтительнее, на мой взгляд.

Игорь
Игорь
Reply to  Dmitry R
4 лет назад

Так не надежно: если будет сбой в процессе перепрошивки, например питание пропадет, может испортится программа во flash и всё, без программатора прибор не восстановишь.

Dmitry R
Dmitry R
5 лет назад

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

wSergey
wSergey
5 лет назад

А вот такой вопрос: Мы используем для загрузчика библиотеку FatFs, которая занимает бОльшую часть этого загрузчика. А, допустим, в проекте я тоже буду задействовать эту библиотеку. Но не добавлять же её второй раз в основную программу?! Можно как-то пользоваться функциями, которые уже зашиты в бутлоадер? Я так понимаю нужно только знать по какому адресу у нас лежат эти функции и скормить это дело линковщику. Как это сделать?

Erik_20
Erik_20
5 лет назад

А как можно сделать этот же загрузчик, но только не используя внешнею память? В моем проекте нету SD-карты, только сам smt32f103. И я не знаю, что делать с условием если ключ не совпал. Где мне искать? Куда я его должен записать файл прошивки? Заранее спасибо. 🙂

Erik_20
Erik_20
5 лет назад

А не подскажите какие команды должны использоваться? У меня мало опыта в таких делах. Поэтому не знаю как инициализировать flash-память виде внешнего устройства. И я правильно понимаю что адрес куда будет копироваться прошивка, должен быть после адреса где лежит boot? Подскажите как это должно выглядит. Премного благодарен. 🙂

Erik_20
Erik_20
5 лет назад

Спасибо). Забыл сказать у меня файл прошивки проходит по локалький. Получается я ведь не смогу использовать метод MSD, но flash-память можно будет использовать как внешний накопитель. Так ведь? Или я не прав?

Erik_20
Erik_20
5 лет назад

Еще один глупый вопрос. Получается файл прошивки будет загружаться в тот же сектор, который в последствии будет обновляться? Или же его нужно загружать в отдельный сектор, а в оставшиеся свободные сектора будет записывается будущая программа? Заранее спасибо. 🙂

Erik_20
Erik_20
5 лет назад

Понял. Большое спасибо. 🙂

Юрий
Юрий
4 лет назад

Попробовал для stm32f051 под IAR7. Не могу разобраться с переносом векторов прерывания: на NVIC_SetVectorTable компилятор ругается, в system_stm32f0xx.с тоже не нахожу возможности менять адрес.

mitek
mitek
4 лет назад

а еще лучше прочитать описание STM32F0 и там написано что там нет регистра таблицы векторов – только 0х8000000 или 0х20000000 – так что копируйте в память и пользуйтесь

Руслан
Руслан
4 лет назад

а как вы узнали что 256 страниц и что они по 2 кБ
у меня STM32L151cbt6 перерыл даташит не нашёл(((

Руслан
Руслан
4 лет назад

спасибо, нашёл

V
V
4 лет назад

У Вас, случайно, нет исходника штатного загрузчика для микроконтроллера STM32F103 или какого-нибудь частного примера на его основе (в инете примера пока найти не удалось)?
Возникла необходимость реализации обновления прошивки с помощью Flash Loader demonstrator по другому каналу USART. Вдобавок, обновление будет осуществляться по RS-485, то есть загрузчик будет дергать линию направления передачи.
Ткните, пожалуйста, если знаете, где можно взять пример на основе стандартного загрузчика.

Евгений
Евгений
4 лет назад

Дмитрий, а если в момент перепрошивки пропадет питание? Загрузчик в RAM, понятное дело, умрет, при этом Flash-память корректно записана не будет. Получим мертвую железку, уже не способную к самообновлению, не так ли? Воскресить ее можно будет только с помощью программатора, т.е. силами разработчика, но не конечного юзера – мы же не будем давать последнему в руки программатор и незашифрованную прошивку. В этом смысле независимый Flash-загрузчик более безопасен: не успели записать основную программу – не беда, можно попробовать еще раз. Или все-таки есть способ исключить вероятность такой проблемы, не отказываясь от RAM-загрузчика?

Олег
Олег
Reply to  Евгений
3 лет назад

Продублируй питание ионистором и будет тебе счастье

Apparatchik
Apparatchik
4 лет назад

Скажите пожалуйста разве адрес 0x0800A000 это адрес не 20ой страницы или я не правильно считаю?

Apparatchik
Apparatchik
4 лет назад

Ну вот и прибавляю 20 * 0x800 + 0x08000000 = 0x0800A000 тоесть это начало 20ой страницы а не 21ой.

Apparatchik
Apparatchik
4 лет назад

Вы забыли про нулевую страницу.

Apparatchik
Apparatchik
4 лет назад

У меня не выходит перейти на основную программу.

void jumpToApplication(uint32_t address) {
typedef void (*pFunction)(void);
pFunction Jump_To_Application;
uint32_t JumpAddress;

JumpAddress = *(uint32_t*) (address + 4);
Jump_To_Application = (pFunction) JumpAddress;
// Initialize user application’s Stack Pointer
__set_MSP(*(vu32*) address);
Jump_To_Application();
}

__disable_irq();
NVIC_SetVectorTable(NVIC_VectTab_FLASH, MAIN_PROGRAM_START_ADDRESS);
jumpToApplicatio (MAIN_PROGRAM_START_ADDRESS);

и ничего не происходит. Основная программа залита и в st-linc видно что начинается с нужного адреса.

Apparatchik
Apparatchik
4 лет назад

Отладчика нету, пишу под GCC в файле скрипта линкера указал начало ROM с нужного адреса. Подскажите что я есче не сделал.

Apparatchik
Apparatchik
4 лет назад

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

Apparatchik
Apparatchik
4 лет назад

Объясните плиз как Вы проверяете наличие записаной основной программы
if (((*(uint32_t*)MAIN_PROGRAM_START_ADDRESS) & 0x2FFF0000 ) == 0x20000000)
откуда берутся эти числа?

Алексей
Алексей
4 лет назад

Добрый день. Спасибо за информацию. Думаю решить свою задачу на основе вашей статьи. Суть задачи: Обновлять прошивку через внешнюю программу с PC. Написал на visual studio не большую программку для настройки устройства (собран на STM32F303CBT6), теперь хочу из под этой же программы обновлять прошивку. Решение вижу таким. По нажатию кнопки стираю BOOTLOADER_KEY_VALUE и перезапускаю контроллер. Далее в bootloader общаюсь с STM через USB CDC и записываю основной код программы по адресу 0x0800A000+4 (как это делать тоже пока не знаю, но в основной программе уже обмениваюсь данными с PC туда-сюда. Не знаю как загружать файл в STM. Но с этим потом разберусь). Заново прописываю BOOTLOADER_KEY_VALUE и перезапускаю STM. Теперь перейдем к реализации. Прописываю void SetKey(), void ResetKey(), uint32_t ReadKey(). И сразу выдает ошибки
Bootloader\Bootloader.axf: Error: L6218E: Undefined symbol FLASH_Lock (referred from main.o).
Направьте на путь истинный с чем это связано.
Может ткнете носом в уже готовое решение, что бы сделать по аналогии.
Как вы поняли по вопросу в STM я любитель.

Алексей
Алексей
Reply to  Aveal
4 лет назад

Использую HAL. Ну вот буду дальше ковырять. Если есть мысли по реализации моей задачи, готов выслушать.

Алексей
Алексей
4 лет назад

Да примерно такая мысль. Думаю еще отправить длину прошивки при начале программирования. Остался вопрос как брать из прошивки N байт))).

Алексей
Алексей
4 лет назад

Спасибо. Немного запутался с теорией. Думаю зачем мне перегружать контроллер при начале прошивке. Ведь можно при запуске сразу отправлять по адресу 0x0800A000. но в bootloader расположить функцию которая будет прописывать полученные данные во Флэш (при обращении к ней из основной программы) и потом перегружать. Или так не прокатит?

Алексей
Алексей
Reply to  Aveal
4 лет назад

В основной USB CDC нужен. Буду начинать…..

Алексей
Алексей
Reply to  Алексей
4 лет назад

не проглатывает pFunction
когда объявляю pFunction Jump_To_Application;
Это тоже связано с HAL?

Алексей
Алексей
4 лет назад

Пробую вариант с морганием светодиодом, не получается перейти к основной программе.
__disable_irq();
NVIC_SetVectorTable(NVIC_VectTab_FLASH, MAIN_PROGRAM_START_ADDRESS);

jumpAddress = *(__IO uint32_t*) (MAIN_PROGRAM_START_ADDRESS+4);
Jump_To_Application = (pFunction) jumpAddress;
__set_MSP(*(__IO uint32_t*) MAIN_PROGRAM_START_ADDRESS);
Jump_To_Application();
ругается на
NVIC_SetVectorTable.

Алексей
Алексей
4 лет назад

Еще раз спасибо. Очень полезная информация. С bootloader-ом все получилось (переключаться из основной программы в бутлоадер и наоборот). Сейчас буду пробовать заливать прошивку.
if (ReadKey() == BOOTLOADER_KEY_VALUE)
{
ResetKey();
jumpToApplication(MAIN_PROGRAM_START_ADDRESS);
}
else
SetKey();
после каждой перезагрузки запускается или boot или основная программа

Алексей
Алексей
4 лет назад

Добрый день. Возвращаюсь к задаче. Получилось программно переключаться между bootloader и main программой. Но при переходе в bootloader соответственно слетает com port и программа теряет устройство. Подскажите как можно это решить. Есть еще одно решение: в устройстве используется EEPROM. Может из под программы записать прошивку в eeprom. А потом после перехода в bootloader прошивать из eeprom.

Алексей
Алексей
4 лет назад

Дошел до прошивки в контроллер и вот такая беда.
Повторюсь, что использую HAL и STM32F303CB.
HAL_FLASH_Unlock();
for(uint8_t i = 0; i < (NUM_OF_PAGES – MAIN_PROGRAM_PAGE_NUMBER); i++)
{ FLASH_PageErase(MAIN_PROGRAM_START_ADDRESS + i * FLASH_PAGE_SIZE);
}
Write = 0x11223344;
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, MAIN_PROGRAM_START_ADDRESS, Write);
HAL_FLASH_Lock();
Read = (*(__IO uint32_t*) MAIN_PROGRAM_START_ADDRESS);
if(Write != Read)
error = 1;
else
error = 0;
Стирание работает (все забивает единицами) и записи нет. Когда считываю получаю Read = 0xffffffff.
В чем может быть беда?

Алексей
Алексей
Reply to  Aveal
4 лет назад

По отдельности
вот так стирается страница
HAL_FLASH_Unlock();
FLASH_PageErase(0x0800A000);
HAL_FLASH_Lock();
так записывается
HAL_FLASH_Unlock();
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 0x0800A000, 0x11223344);
HAL_FLASH_Lock();
А если все устанавливаю подряд, то стирает, но не записывает
HAL_FLASH_Unlock();
FLASH_PageErase(0x0800A000);

HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 0x0800A000, 0x11223344);
HAL_FLASH_Lock();
Пробовал и через delay.

Алексей
Алексей
4 лет назад

Появилась минута попробовать.
Вот с этим HAL_FLASHEx_Erase() все работает как положено. Низкий поклон Вам.

Алексей
Алексей
4 лет назад

Ну может тогда добьем тему Bootloader.
Повторюсь. Хочу прошивать из под своей программы. Подключено устройство к PC как USB CDC. Через программу прошиваю вместо BOOTLOADER_KEY_VALUE значение равное размеру файла прошивки и перевожу контроллер из основной программы в бутлоадер. В программе закрываю COM порт, жду например 2 сек (пока контроллер перегружается) и заново открываю порт, но подключиться уже не могу. Естественно COM порт слетает хотя номер его остается таким же. Подскажите как можно это решить эту беду.

Алексей
Алексей
Reply to  Aveal
4 лет назад

Да, определяется и не пропадает после перехода в бут. Пока не отключу и заново не включу кабель USB в комп ничего сделать не получается (Контроллер при этом не отключаю, он запитывается отдельно не от USB )

Алексей
Алексей
4 лет назад

считается ли перезапуском , если я просто отправляю программу по другому адресу?

void jumpToApplication(uint32_t addr)
{
typedef void (*pFunction)(void);
pFunction Jump_To_Application;
uint32_t JumpAddress;

JumpAddress = *(__IO uint32_t*) (addr + 4);
Jump_To_Application = (pFunction) JumpAddress;
__set_MSP(*(__IO uint32_t*) addr);
Jump_To_Application();
}

Алексей
Алексей
Reply to  Aveal
4 лет назад

перегружаю вот так
NVIC_SystemReset();
но это не решает проблему. И похоже надо как то в ноутбуке переопределять порты. Потому что пока не переконнектю USB кабель ничего делать не дает (контроллер при этом не перегружаю). Буду сегодня воевать с этим.

Алексей
Алексей
Reply to  Aveal
4 лет назад

Вот какая ситуация. При перезагрузке контроллера COM порт не пропадает в диспетчере. Обновление конфигурации тоже не помогает. Пока не удалю его из системы и заново не обновлю конфигурацию оборудования или переконнектю USB.
Если просто закрываю порт в программе а потом заново открываю выдает ошибку при открытии.

после отправки команды перехода в main или в boot сделал программно вот так (жду 5 сек пока устройство перегрузится)

serialPort1.Close();
Thread.Sleep(5000);

serialPort1.Open();

Алексей
Алексей
Reply to  Aveal
4 лет назад

Попробовал – не помогло. Может конечно что то не верно делал. Но по ходу заметил такую вещь. Я использую для тестов stm32f3 discovery. Если контролер подключен к компьютеру как CDC и на нем нажать кнопку ресет, то COM порт уже не подключается. Так что здесь вопрос не в программе bootloader.

Алексей
Алексей
Reply to  Aveal
4 лет назад

может не совсем верно написал. Последовательность такая.
Есть монитор порта COM Port toolkit. Подключаю плату к ноутбуку, определяется COM port и программа нормально подключается.
Если же подключить плату, определится COM порт в системе и нажать кнопку reset. Com port в это время не пропадает и не переопределяется. Но COM Port toolkit уже не может подключиться к порту (выдает ошибку). И так пока заново не переконнектить USB или не удалить устройство из системы и обновить конфигурацию.

Алексей
Алексей
Reply to  Алексей
4 лет назад

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

Алексей
Алексей
4 лет назад

Взял другой ноутбук, Windows 7. Ситуация та же самая. Так что вопрос ОС отпадает.
ты отключал USB вот так:
USBD_Stop()
USBD_DeInit()
?
сори за ТЫКАНИЕ.

Алексей
Алексей
4 лет назад

Видать я как то не так отключаю. Буду разбираться.
я написал вот так
if(((HAL_GetTick() / 5000) % 2) == 0)
{
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_11, 1);
void MX_USB_DEVICE_Init(void);
}
else
{
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_11, 0);
MX_USB_DEVICE_DeInit();
}

Инициализация вот так
void MX_USB_DEVICE_Init(void)
{
/* Init Device Library,Add Supported Class and Start the library*/
USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);

USBD_RegisterClass(&hUsbDeviceFS, &USBD_CDC);

USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_Interface_fops_FS);

USBD_Start(&hUsbDeviceFS);

}
и отключение вот так.
void MX_USB_DEVICE_DeInit(void)
{
/* Init Device Library,Add Supported Class and Start the library*/

USBD_Stop(&hUsbDeviceFS);

USBD_DeInit(&hUsbDeviceFS);

}
при включении сразу определилось устройство и все

Алексей
Алексей
4 лет назад

Определился что повторная инициализация USB вызывает такую проблему, а вот как деинициализоровать USB через HAL понять не могу. По идее после деинициализации com порт должен пропасть из устройств….. НО ни в какую не выходит. Жду подсказок ГУРУ.

Алексей
Алексей
4 лет назад

Подскажи пожалуйста на чем ты проверяешь и как реализована подтяжка к +3,3v 3 пина USB разъема(D+). Пока его не сброшу система не переопределяет оборудование.

Алексей
Алексей
4 лет назад

Не знаю верно или нет, но решил проблему вот так.

USBD_Stop(&hUsbDeviceFS);
USBD_DeInit(&hUsbDeviceFS);

GPIO_InitTypeDef DP_Init;
DP_Init.Pin = GPIO_PIN_12;
DP_Init.Mode = GPIO_MODE_OUTPUT_PP;
DP_Init.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOA, &DP_Init);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, 0);
После отключения USB переназначаю DP пин и прописываю туда 0. и перезагружаю МК.
Вот теперь он переопределяет COM порт.
Если есть более мудрые решения – готов выслушать.

Володимир
Reply to  Алексей
3 лет назад

Алексей, вы нигде не упоминали в чем вы пишете, в Keil или IAR? Вы в своей функции jumpToApplication(uint32_t addr) не выполняете команду переноса таблицы векторов SCB->VTOR = addr;
Значит у вас в загруженной программе прерывания выполняются функциями бутлоадера. Может знаете как перенести таблицу прерываний в другое место?

яга
яга
4 лет назад

здравствуйте.
хочу прошивку передавать по USART1 через Rx|Tx и что-то не могу сообразить как правильно.
вот что я написал, НО ума не приложу, как мне все остальное реализовать… 🙁

1. данные пытаюсь беззнаковыми чарами брать (кажется мне это не правильно)
2. данные хочу передавать пачками (не могу подобрать размер какой лучше)
2.1 при получении пачки (т.е. внутри функции HAL_UART_RxCpltCallback) считать контрольную сумму и передавать ее отправителю, чтобы тот решил все ли правильно передано или нет
2.2 если возникла ошибка – переотправить данные ИНаче следующую пачку отправлять
3. при получении каждой пачки “дозаписывать” по Флеш
4. последняя передача – это получение кода, по которому будет ясно, что прошивка вся передана, тогда выставляю флаг и перепрыгиваю на эту программу (пока без перезапуска)

static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
uint8_t i;
if(huart->Instance == USART1){
// __HAL_UART_FLUSH_DRREGISTER(&huart1); // clear the buffer
if( Rx_indx==0){ for(i=0; i<UART_BUFFER_SIZE; i++) Rx_buffer[i] = 0; } // clear Rx_buffer receiving new data
if(Rx_data[0]!=13){
Rx_buffer[Rx_indx++] = Rx_data[0];
}else{
Rx_indx = 0;
Transfer_cplt = 1; // transfer complete, data is ready to read
}
HAL_UART_Receive_IT(&huart1, Rx_data, 1);
}
}

void ResetFlash(){
HAL_FLASH_Unlock();
HAL_FLASH_Program_IT(TYPEERASE_PAGES, BOOTLOADER_KEY_START_ADDRESS, 0xffff);
HAL_FLASH_Lock();
}

void WriteToFlash(unsigned char data){
HAL_FLASH_Unlock();
HAL_FLASH_Program_IT(TYPEPROGRAM_WORD, BOOTLOADER_KEY_START_ADDRESS, data);
HAL_FLASH_Lock();
}

int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_IT(&huart1, Rx_data, 1); // activate UART RX interrupt

/* Infinite loop */
while (1)
{
if(Transfer_cplt){
//TODO: get firmware
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_9);
//TODO: write to flash
ResetFlash();
WriteToFlash(Rx_buffer);
//TODO: restart stm32
//NVIC_SystemReset(); // перезагрузка контроллера
//TODO: move vector table
//TODO: jump to Application

}

яга
яга
Reply to  Aveal
4 лет назад

дак а мне:
1- все-таки какой тип использовать для получаемых байтов?
2- как примерно рассчитывается размер массива для получаемых данных?

подскажите, пожалуйста

Дима
4 лет назад

Зачем переносить ВП и изменять указатель стека?
Делаю бутлоадер и пока не разобрался. Думается что лучше оставить всё на месте, разместить функцию main по известному адресу. Бутлоадер-функцию вызывать раньше main. Из бинарника брать данные по известному адресу и перезаписывать main.
Буду рад детальнее услышать про SP и Heap!

Марат
3 лет назад

Все описанное работает “на ура”, до тех пор, пока USER_APPLICATION – это мигающий светодиод в main();
Вопрос (бьюсь уже давно) – кто нибудь пробовал при помощи STM32Cube собрать приложение с использованием FreeRTOS и адресом старта (например с 0x008010000)?
Это к вопросу где размещать bootloader. Если bootloader в начало (0x008000000), а потом само приложение – то FreeTROS падает на запуске шедулера.
Таблицу векторов переключаю (прерывания заводятся и живут), в линкере все прописано на новые адреса. Но задачи (UserTascks) не запустить. То же самое приложение (слинкованное на 0x008000000 – работает).
КАК заставить FreeRTOS жить с другим стартовым адресом???

Пока bootloader у меня живет в конце памяти (под одно-поточным main()). Приложение по кнопке на старте прыгает на bootloader. Обновляемся. И обратный прыжок в начало на 0x008000000.

ctef
ctef
Reply to  Марат
3 лет назад

Была этаже проблема с FreeRTOS
нужно запретить SysTick timer

void boot_jump( uint32_t address )
{
__ASM volatile (“MOV LR, #0xFFFFFFFF”); //Reset Link Register
__ASM volatile (“LDR SP, [R0]”); //Load new stack pointer address
__ASM volatile (“LDR PC, [R0, #4]”);//Load new program counter address
}

void jumpToProgram (uint32_t address)
{
SCB->VTOR = address & 0x3FFFFF80;
boot_jump(address);
}

void execute_bank_a_user_code(void)
{
/* Disable SysTick timer */
SysTick->CTRL &= ~(SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk);
/* Disable INT */
disable_interrupts();

jumpToProgram((uint32_t)USER_START_SECTOR_ADDRESS);
}

Владимир
Владимир
Reply to  ctef
3 лет назад

Keil выдает ошибки на эти команды:
__ASM volatile («MOV LR, #0xFFFFFFFF»); //Reset Link Register
__ASM volatile («LDR SP, [R0]»); //Load new stack pointer address
__ASM volatile («LDR PC, [R0, #4]»);//Load new program counter address

Shank
Shank
3 лет назад

Здравствуйте, а можно реализовать прошивку через блютуз(hc-05)?

Shank
Shank
Reply to  Aveal
3 лет назад

Не подскажете, как можно реализовать?
Принял порцию байт на Rx – записал во флеш?
И какими порциями лучше всего отправлять?

Shank
Shank
Reply to  Aveal
3 лет назад

А не будет проблем из-за асинхронности? То есть он будет успевать принимать новую порцию байт, пока записывает уже полученную во флеш?

Shank
Shank
Reply to  Aveal
3 лет назад

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

Shank
Shank
Reply to  Aveal
3 лет назад

Ну, я новичок в этом, но постараюсь реализовать, если получится – отпишусь)

Shank
Shank
Reply to  Aveal
3 лет назад

Нашел одну реализацию бутлоадера через юсарт интерфейс, немного подредактировал его под себя, но что-то не хочет работать, можете подсказать – в чем ошибка?

Shank
Shank
Reply to  Aveal
3 лет назад

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

Shank
Shank
Reply to  Aveal
3 лет назад

И еще у вас в NVIC_SetVectorTable вторым аргументом указан абсолютный адрес, а нужно смещение.

Shank
Shank
Reply to  Aveal
3 лет назад

Был бы очень благодарен.

Отладчиком проверил – почему-то в JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4); – джамп адрес равен 0x00000000 и при вызове Jump_To_Application(); – пишет cannot access memory.

Shank
Shank
Reply to  Aveal
3 лет назад

hardfault кидает при заходе в Jump_To_Application();

Shank
Shank
Reply to  Aveal
3 лет назад

А можно адрес?

Да, почему-то не присваивается.

Shank
Shank
Reply to  Aveal
3 лет назад

Готово

Egor
3 лет назад

Добрый день. Подскажите пожалуйста.

Есть плата (разрабатывается) на на базе stm32f103ret6, к ней подключена память AT45DB321D
В зависимости от выставленных перемычек и прошивки плата выполняет функции.

1) Будет ли bootloader работать с AT45DB321D ?

2) Если будет тогда я скажите я правильно понял…
– В dataFlast по адресу х1 записана прошивка pr1,
по адресу х2 записана прошивка pr2

Мой алгоритм соответственно такой
-> Инициализация периферии
-> При срабатывании EXTI(необходимая перемычка) выбирается адрес прошивки
-> При сработки BOOT0 и/или BOOT1 (подскажите какой в какое положение)
-> Загружается прошивка по нужному адресу
-> Перезагружаем контроллер
В данном случае стирать прошивку не нужно.

Egor
Reply to  Aveal
3 лет назад

Да

Egor
Reply to  Aveal
3 лет назад

А если я во внутреннюю flash буду писать? я смогу выбрать версию прошивки(по начальному адресу)? или GPIO то же нельзя&

Egor
Reply to  Aveal
3 лет назад

спасибо

Владимир
Владимир
3 лет назад

В IAR работает, а в Keil с HAL не работает переход. Неделю мучаюсь с переносом прерываний, без них работает. Может кто сталкивался с этой проблемой, и подскажет что делать.

Присоединяйтесь!

Profile Profile Profile Profile Profile
Vkontakte
Twitter

Язык сайта

Август 2020
Пн Вт Ср Чт Пт Сб Вс
 12
3456789
10111213141516
17181920212223
24252627282930
31  

© 2013-2020 MicroTechnics.ru