Top.Mail.Ru

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

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

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

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

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

В нашем примере в качестве ключа будет выступать значение 0xAAAA5555, и храниться ключ будет в 19-ой странице 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] = '\0';

	result = f_open(&program, (char*)path, FA_READ);

	if (result == FR_OK)
	{
		// Program........

И если нужный нам файл найден, то следует произвести программирование. Реализуем это, и для начала разблокируем 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-карты, ведь иначе при каждом перезапуске 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 (пример для IAR):

Bootloader и STM32

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

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

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

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

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

Подписаться
Уведомить о
guest

151 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Sergey
Sergey
9 лет назад

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

Sergey
Sergey
9 лет назад

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

Артур
Ответ на комментарий  Sergey
1 год назад

Добрый день Сергей. Не можете поделиться кодом Bootloader для процессора stm32f407?

Артур
Ответ на комментарий  Sergey
1 год назад

Добрый день Сергей. Не можете поделиться кодом Bootloader для процессора stm32f407? Спасибо.

Ars
Ars
9 лет назад

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

Westbam
Westbam
9 лет назад

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

Westbam
Westbam
9 лет назад

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

Westbam
Westbam
9 лет назад

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

Sergey
Sergey
8 лет назад

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

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

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

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

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

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

Игорь
Игорь
Ответ на комментарий  Dmitry R
7 лет назад

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

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

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

wSergey
wSergey
8 лет назад

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

Takheer
Takheer
Ответ на комментарий  wSergey
1 месяц назад

Можно определить в бутлоадере секцию типа .utils и в линкере положить её после вектора прерываний, например:

SECTIONS
{
  /* The startup code into "FLASH" Rom type memory */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH
  .utils :
  {
    . = ALIGN(4);
    KEEP(*(.utils)) /* Наша секция с указателями на функции из либы */
    . = ALIGN(4);
  } >FLASH


  /* The program code and other data into "FLASH" Rom type memory */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH
}

Дальше где-нибудь в коде бутлоадера прописать массив указателей на функции библиотеки:

__attribute__((section(".utils"), used))
static void **spi_interface[4] = {
  (void*)(spi_open),
  (void*)(spi_close),
  (void*)(spi_write),
  (void*)(spi_read),
}

И теперь можно в коде приложения вызывать эти функции по адресу:

// По этому адресу у меня сейчас конец вектора прерываний и load address для секции .text
static void **spi_interface = (void**)(0x080000с0);

spi_interface[0](); // Вызовется spi_open()

Единственная проблема — теряется сигнатура. Можно сделать адаптеры в коде приложения, если функции несложные и их немного, то проблем может не быть, но если это какая-то библиотека с большим количеством функций — дебаггинг может превратиться в ад. Код адаптера может выглядеть как-то так:

static inline spi_read(void *buf, int size) 
{
  int (*do_read)(void*, int) = (int (*)(void*, int))(spi_interface[3]);
  
  return do_read(buf, size);
}
Erik_20
Erik_20
8 лет назад

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

Erik_20
Erik_20
8 лет назад

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

Erik_20
Erik_20
8 лет назад

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

Erik_20
Erik_20
8 лет назад

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

Erik_20
Erik_20
8 лет назад

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

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

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

mitek
mitek
8 лет назад

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

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

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

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

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

V
V
8 лет назад

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

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

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

Олег
Олег
Ответ на комментарий  Евгений
7 лет назад

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

Apparatchik
Apparatchik
8 лет назад

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

Apparatchik
Apparatchik
8 лет назад

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

Apparatchik
Apparatchik
8 лет назад

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

Apparatchik
Apparatchik
8 лет назад

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

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
8 лет назад

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

Apparatchik
Apparatchik
8 лет назад

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

Apparatchik
Apparatchik
8 лет назад

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

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

Добрый день. Спасибо за информацию. Думаю решить свою задачу на основе вашей статьи. Суть задачи: Обновлять прошивку через внешнюю программу с 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 я любитель.

Алексей
Алексей
Ответ на комментарий  Aveal
7 лет назад

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

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

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

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

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

Алексей
Алексей
Ответ на комментарий  Aveal
7 лет назад

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

Алексей
Алексей
Ответ на комментарий  Алексей
7 лет назад

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

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

Пробую вариант с морганием светодиодом, не получается перейти к основной программе.
__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.

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

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

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

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

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

Дошел до прошивки в контроллер и вот такая беда.
Повторюсь, что использую 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.
В чем может быть беда?

Алексей
Алексей
Ответ на комментарий  Aveal
7 лет назад

По отдельности
вот так стирается страница
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.

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

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

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

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

Алексей
Алексей
Ответ на комментарий  Aveal
7 лет назад

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

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

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

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();
}

Алексей
Алексей
Ответ на комментарий  Aveal
7 лет назад

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

Алексей
Алексей
Ответ на комментарий  Aveal
7 лет назад

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

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

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

serialPort1.Open();

Алексей
Алексей
Ответ на комментарий  Aveal
7 лет назад

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

Алексей
Алексей
Ответ на комментарий  Aveal
7 лет назад

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

Алексей
Алексей
Ответ на комментарий  Алексей
7 лет назад

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

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

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

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

Видать я как то не так отключаю. Буду разбираться.
я написал вот так
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);

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

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

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

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

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

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

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

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 порт.
Если есть более мудрые решения - готов выслушать.

Володимир
Ответ на комментарий  Алексей
7 лет назад

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

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

здравствуйте.
хочу прошивку передавать по 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

}

яга
яга
Ответ на комментарий  Aveal
7 лет назад

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

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

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

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

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

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

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

ctef
ctef
Ответ на комментарий  Марат
7 лет назад

Была этаже проблема с 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);
}

Владимир
Владимир
Ответ на комментарий  ctef
7 лет назад

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
7 лет назад

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

Shank
Shank
Ответ на комментарий  Aveal
7 лет назад

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

Shank
Shank
Ответ на комментарий  Aveal
7 лет назад

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

Shank
Shank
Ответ на комментарий  Aveal
7 лет назад

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

Shank
Shank
Ответ на комментарий  Aveal
7 лет назад

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

Shank
Shank
Ответ на комментарий  Aveal
7 лет назад

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

Shank
Shank
Ответ на комментарий  Aveal
7 лет назад

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

Shank
Shank
Ответ на комментарий  Aveal
7 лет назад

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

Shank
Shank
Ответ на комментарий  Aveal
7 лет назад

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

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

Shank
Shank
Ответ на комментарий  Aveal
7 лет назад

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

Shank
Shank
Ответ на комментарий  Aveal
7 лет назад

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

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

Shank
Shank
Ответ на комментарий  Aveal
7 лет назад

Готово

Egor
7 лет назад

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

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

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

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

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

Egor
Ответ на комментарий  Aveal
7 лет назад

Да

Egor
Ответ на комментарий  Aveal
7 лет назад

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

Egor
Ответ на комментарий  Aveal
7 лет назад

спасибо

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

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

151
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x