Микроконтроллер и Bootloader. Практическая реализация для STM32.

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

Создание загрузчика

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

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

Для начала определим нужные нам адреса, а также параметры 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] = '\0';
 
  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-карты, ведь иначе при каждом перезапуске 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 Regoins в поле ROM Start надо указать тот же адрес — 0x0800A000.

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

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

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

Понравилась статья? Поделись с друзьями!

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

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

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

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

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

    • А смысл иметь несколько загрузчиков? Можно и 10 штук их записать и первый будет обновлять все остальные.

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

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

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

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

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

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

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

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

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

    • Можно сделать так, чтобы при подключении по USB сама flash-память контроллера определялась как внешний накопитель. И тогда просто скопировав туда прошивку она будет прошиваться в нужные сектора.

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

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

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

    • Ты можешь делать абсолютно любым способом, главное, чтобы цель была достигнута — чтобы прошивка программировалась в нужные сектора (свободные от бутлодера) и нормально запускалась =))

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

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

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

    • В reference manual по идее должен быть раздел про память. Только именно в reference manual а не в даташите.

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

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

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

    • Можно просто по 0x800 (2048) прибавлять:
      Начало первой страницы:
      0x08000000
      Начало второй:
      0x08000800

      И должно получиться начало 21-ой:
      0x0800A000

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

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

    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 видно что начинается с нужного адреса.

    • Под отладчиком можно проверить — попадает ли программа на нужный адрес, если да, то значит проблема в основной программе.

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

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

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

    • По адресу лежит Stack Pointer, мы его тут проверяем на корректность — его значение должно лежать от 0x20000000 до 0x2000FFFF.

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

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

        • Да мысль то простая ) Берешь пример и тот же самый код переделываешь под HAL. Принял по USB какой-то количество байт — прошил во Flash. Я бы делал так:
          1. Отправляешь на ПК запрос.
          2. В ответ получаешь N байт.
          3. Записываешь их в Flash
          4. Возвращаешься к пункту 1.

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

    • Ну в программе для ПК ты загружаешь бинарник, а уж в .NET полно функций по работе с файлами, просто будешь читать из файла по N байт в массив.

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

    • Лучше всего так:
      В основной программе по приему определенной команды от ПК переходить в бутлодер и начинать процедуру перепрошивки.

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

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

    • Ошибка та же, это функция из SPL, тем более для STM32F10x. В STM32 нельзя так просто взять другой контроллер и библиотеки и просто скопировать код из примера для другого контроллера.

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

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

    • Можно и так, но просто лишние операции добавляются особо не нужные.

      В бутлодере должно все с нуля инициализироваться — USB итд

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

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

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

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

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

        • Вообще при перезапуске устройство в диспетчере должно пропадать, а при запуске бутлодера появляться снова.

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

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

    • Нет, лучше через полную перезагрузку и полностью переинициализировать всю периферию в бутлодере.

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

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

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

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

        serialPort1.Open();

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

        • Ну это как раз правильно — устройство фактически отключено при нажатии ресета, соответственно, подключиться к нему нельзя.

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

        • Тогда может дело в самой системе?
          Я сейчас вот проверил на MSD. Если вызвать функцию отключения USB, то устройство сразу же пропадает из диспетчера устройств. Причем кабель остается воткнут и контроллер продолжает работать.

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

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

    • Да ничего )

      Да, именно так:

      MX_USB_DEVICE_Init();

      HAL_Delay(12000);

      USBD_Stop(&hUsbDeviceHS);
      USBD_DeInit(&hUsbDeviceHS);

      Устройство появляется в системе, потом пропадает, когда задержка проходит. Это для Mass storage.

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

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

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

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

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

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

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

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

    }

    • Добрый день!

      Задумка в целом то правильная. Могу посоветовать для начала без прерываний, просто последовательными вызовами функций все осуществить для тестирования. Чтото вроде такого:

      while(1)
      {
      Отправляем запрос на получение N байт
      Получаем N байт через USART
      Прошиваем во Flash
      }

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

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

        • Размер массива можно любой задать, просто нужно сделать одинаковый на приемной стороне и принимающей. Тут зависит уже от сторонних факторов. Если проект большой и оперативной памяти мало свободной, то есть смысл принимать небольшие пакеты, чтобы не нужно было хранить в оперативной памяти больших объемов данных.

          Тип — unsigned char — берется бинарный файл прошивки и побайтно высылается по USART.

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

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

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

    • Странно, вообще freertos должна работать — достаточно в настройках линкера поменять и вектора сдвинуть.

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

      • 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

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

        • Ну да, все верно.
          Размер зависит, к примеру, от количества свободной оперативной памяти. Если ее мало, то принимать небольшое количество данных и прошивать, если запас есть, можно по 512 байт.

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

        • Нужно обязательно программно обеспечивать эту синхронность. Я, к примеру, делаю так — отправка новых данных из файла прошивки осуществляется только тогда, когда устройство прислало запрос. То есть контроллер завершает запись во Flash и запрашивает новую порцию данных.

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

        • Статья вообще планируется на эту тему, но вот когда точно — через месяц или через 3, я пока не могу сказать..

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

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

        • Так сложно сказать, почему именно не работает — это надо вооружаться отладчиком и все полностью проверять — на каком именно этапе начинаются ошибки.

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

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

        • Могу посмотреть проект, но вряд ли там что-то сразу бросится в глаза, из-за чего может не работать. Скорее всего все-таки нужно будет с отладчиком ковыряться.

          Ну код весь боевой, проверенный, значит в той версии SPL нужно было указывать именно адрес, а не смещение.

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

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

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

        • Присылай на почту.

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

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

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

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

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

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

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

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

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

        • Так не получится — контроллер не сможет переходить по адресам внешней памяти, на то она и внешняя — там же SPI надо использовать, чтобы ее читать/писать.

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

        • Из внешней flash писать во внутреннюю? Так можно. В принципе Bootloader — это отдельная программа — соответственно, в нем можно делать все то же, что и в обычной программе.

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

  47. Здравствуйте. А подскажите мне, в каких случаях для хранения образа прошивки лучше использовать внешнюю память(SPI-flash) а для каких внутреннею STM32?
    Проект занимает 100кб на кристалле STM32(512кб)

    • Добрый день!

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *