STM32 bootloader DFU mode с использованием CubeMX. Инструкция пошаговая, step by step

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

Великое множество обнаруженных в сети bootloader-ов, иногда весьма занятных, к сожалению «заточены» под какой-либо конкретный кристалл.

Предлагаемый материал содержит процедуру использования пакета CubeMX, «загружалки» DfuSeDemo и утилиты подготовки прошивки Dfu file manager, т. е. Мы абстрагируем наши «хотелки» от железки, да простят меня гуру макроассемблера и даташита.

Готовим окружение…

Нам необходимы собственно сам CubeMX, загружалка DfuSeDemo+Dfu file manager, лежат в одном пекете, STM32 ST-LINK Utility, все изыскиваем на сайте STMicroelectronics совершенно бесплатно.

Наша подопытная жлезка с чипом STM32F103C8T6 от дядюшки Ляо

image

и программатор ST-Link оттуда же.

image

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

Итак, поехали…

Запускаем CubeMX и выбираем свой кристалл…

image

Отмечаем свои хотелки…

image

В данной конкретной задаче активируем устройство USB→Device FS и соответсвенно USB Device→ DownLoad Update Firmware class, и незабываем RCC→High Speed Clock→Cristal/Ceramic Resonator тот что на борту платы.

Далее необходимо выбрать переключалку режима bootloader-a, в данном примере просто задействуем имеющуюся перемычку boot1.

image

Смотрим схемку и в соответствии с ней boot1 прицеплен к ноге PB2, вот ее и задействуем в режиме GPIO_Input.

Готово, активируем закладку Clock Configuration и запускаем автомат выбора конфигурации.

image
Прыгаем на закладку Cofiguration…

image

Выбираем кнопку GPIO…

image

И в поле…

image
пишем пользовательскую метку, пусть это будет boot1.

Далее настраиваем проект…

image

Выбираем Project → Setting…

image

Выбираем и заполняем….

image

Соответсвенно выбираем для какого IDE нам Cub сгенерит проект, в нашем случае, MDK-ARM V5.

Закладку Code Generator в данном воплощении оставим без изменений…

image

Ну собственно и все, запускаем генерацию проекта Project→Generate Code

image

По окончании Cub предложит сразу запустить вашу IDE… как поступать выбирать вам.

image

image

Запускаем компиляцию и сборку и загрузку в кристалл… F7, F8…

image

Конечный итог…

Переключаем пины на нашей плате в режим работы и подключаем USB кабель…

image

image

Открываем в Windows панель управления→ система→ диспечер устройтв→ USB контроллер. И смотрим список устройств, Windows немого пошуршит и установит драйвер STM Device in DFU Mode (если он уже не стоял).

Итак, драйвер встал и определился, запускаем «загружалку» DfuSeDemo…

image

Смотрим что у нас поймалось DFU Device и дажды кликаем в поле Select Target …

image

Внимательно смотрим и дивимся, что флеш вплоть до адреса 0x0800C000 закрыт для записи и записываем этот адрес, он нам понадобится…

К слову, пробовал на STM32F407VE, там память открыта для записи с 0x08000000 т. е. С самого начала… почему, в нашем варианте не так, неясно, да и не копал, где то зарыто, но явно не прописано, не есть комильфо, потому что большой кусок пропадает безхозно… может кто и подскажет где копать…

Итак, «стрижка только начата»…

Нам понадобится только два файла исходников…

image

Открываем их в IDE и правим- дополняем…

Учитываем, что CubeMX НЕ ТОГАЕТ при перегенерации вставки между USER CODE BEGIN и USER CODE END… там и будем вписывать наши дополнения…

Начнем с main.c

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
typedef  void (*pFunction)(void);

pFunction JumpToApplication;
uint32_t JumpAddress;

/* USER CODE END PV */
.
.
.
/* USER CODE BEGIN 0 */

uint32_t AddressMyApplicationBegin  = 0x0800C000; 
uint32_t AddressMyApplicationEnd = 0x0800FBFC;

/* USER CODE END 0 */
.
.
.
/* USER CODE BEGIN 2 */

	/* Check if the KEY Button is pressed */
if(HAL_GPIO_ReadPin(boot1_GPIO_Port, boot1_Pin ) == GPIO_PIN_SET)
{
	/* Test if user code is programmed starting from address 0x0800C000 */
    if (((*(__IO uint32_t *) USBD_DFU_APP_DEFAULT_ADD) & 0x2FFE0000) == 0x20000000)
    {
	/* Jump to user application */
      JumpAddress = *(__IO uint32_t *) (USBD_DFU_APP_DEFAULT_ADD + 4);
      JumpToApplication = (pFunction) JumpAddress;

      	/* Initialize user application's Stack Pointer */
      __set_MSP(*(__IO uint32_t *) USBD_DFU_APP_DEFAULT_ADD);
      JumpToApplication();
    }
}
  MX_USB_DEVICE_Init(); /* эту функцию просто переносим сверху */

/* USER CODE END 2 */
.
.
.

на этом с main.c все…

переходим на в usbd_conf.h и в

#define USBD_DFU_APP_DEFAULT_ADD     0x0800000

приводим к виду…

#define USBD_DFU_APP_DEFAULT_ADD     0x080C000 // наш адрес записанный на бумажке…

переходим к usbd_dfu_it.c, тут поболее….

.
.
.
/* USER CODE BEGIN PRIVATE_TYPES */
extern uint32_t AddressMyApplicationBegin; 
extern uint32_t AddressMyApplicationEnd;

/* USER CODE END PRIVATE_TYPES */ 
.
.
.
/* USER CODE BEGIN PRIVATE_DEFINES */
#define FLASH_ERASE_TIME    (uint16_t)50
#define FLASH_PROGRAM_TIME  (uint16_t)50
/* USER CODE END PRIVATE_DEFINES */
.
.
.
и собственно правим, а вернее заполняем «пустышки» рабочим кодом…

uint16_t MEM_If_Init_FS(void)
{ 
  /* USER CODE BEGIN 0 */ 
	
	HAL_StatusTypeDef flash_ok = HAL_ERROR;
	
  //Делаем память открытой
  while(flash_ok != HAL_OK){
	   flash_ok = HAL_FLASH_Unlock();
  }	
  return (USBD_OK);
  /* USER CODE END 0 */ 
}
.
.
.
uint16_t MEM_If_DeInit_FS(void)
{ 
  /* USER CODE BEGIN 1 */ 
	
	HAL_StatusTypeDef flash_ok = HAL_ERROR;
	
  //Закрываем память
  flash_ok = HAL_ERROR;
  while(flash_ok != HAL_OK){
    flash_ok = HAL_FLASH_Lock();
  }
  return (USBD_OK);
  /* USER CODE END 1 */ 
}
.
.
.
  uint16_t MEM_If_Erase_FS(uint32_t Add)
{
 /* USER CODE BEGIN 2 */ 
	
	
  uint32_t NbOfPages = 0;
  uint32_t PageError = 0;
  /* Variable contains Flash operation status */
  HAL_StatusTypeDef status;
  FLASH_EraseInitTypeDef eraseinitstruct;

   /* Get the number of sector to erase from 1st sector*/
  NbOfPages = ((AddressMyApplicationEnd - AddressMyApplicationBegin) / FLASH_PAGE_SIZE) + 1;
  eraseinitstruct.TypeErase = FLASH_TYPEERASE_PAGES;
  eraseinitstruct.PageAddress = AddressMyApplicationBegin;
  eraseinitstruct.NbPages = NbOfPages;
  status = HAL_FLASHEx_Erase(&eraseinitstruct, &PageError);

  if (status != HAL_OK)
  {
    return (!USBD_OK);
  }
  return (USBD_OK);
  /* USER CODE END 2 */ 
}
.
.
.
uint16_t MEM_If_Write_FS(uint8_t *src, uint8_t *dest, uint32_t Len)
{
  /* USER CODE BEGIN 3 */ 
	
	uint32_t i = 0;
	
  for(i = 0; i < Len; i+=4)
  {
    /* Device voltage range supposed to be [2.7V to 3.6V], the operation will
       be done by byte */ 
    if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, (uint32_t)(dest+i), *(uint32_t*)(src+i)) == HAL_OK)
    {
//			Usart1_Send_String("MEM_If_Write_FS OK!");
     /* Check the written value */
      if(*(uint32_t *)(src + i) != *(uint32_t*)(dest+i))
      {
        /* Flash content doesn't match SRAM content */
        return 2;
      }
    }
    else
    {

      /* Error occurred while writing data in Flash memory */
      return (!USBD_OK);
    }
  }
	
  return (USBD_OK);
  /* USER CODE END 3 */ 
}
.
.
.
uint8_t *MEM_If_Read_FS (uint8_t *src, uint8_t *dest, uint32_t Len)
{
  /* Return a valid address to avoid HardFault */
  /* USER CODE BEGIN 4 */ 
  uint32_t i = 0;
  uint8_t *psrc = src;

  for (i = 0; i < Len; i++)
  {
    dest[i] = *psrc++;
  }	
  return (uint8_t*)(dest); /* ВНИМАТЕЛЬНО, В ГЕНЕРАЦИИ ПО УМОЛЧАНИЮ ДРУГОЕ*/
  /* USER CODE END 4 */ 
}
.
.
.
uint16_t MEM_If_GetStatus_FS (uint32_t Add, uint8_t Cmd, uint8_t *buffer)
{
  /* USER CODE BEGIN 5 */ 
	
  switch (Cmd)
  {
  case DFU_MEDIA_PROGRAM:
    buffer[1] = (uint8_t)FLASH_PROGRAM_TIME;
    buffer[2] = (uint8_t)(FLASH_PROGRAM_TIME << 8);
    buffer[3] = 0;  
    break;
    
  case DFU_MEDIA_ERASE:
  default:
    buffer[1] = (uint8_t)FLASH_ERASE_TIME;
    buffer[2] = (uint8_t)(FLASH_ERASE_TIME << 8);
    buffer[3] = 0;  
    break;
  }                             
  return  (USBD_OK);
  /* USER CODE END 5 */  
}


Собственно и все…

Подключаем программатор, перекидываем перемычки в режим программирования, F7, F8 и botloader записан…

Можно пользоваться…

Теперь подготовим наше приложение для загрузки посредством bootloder…
Любимое приложение будет моргать светодиодиком…

Готовим и отлаживаем приложение, и меняем в компиляторе и теле программы отдельные места на предмет изменения адреса запуска программы и векторов прерываний…

А именно в KEIL → Configure → Flash Tools

image

Меняем адрес начала программы…

image

Говорим чтобы генерировал HEX файл

image

и меняем адрес таблицы векторов…

собираем программу F7…

полученный HEX преобразуем в dfo файл утилитой Dfu file manager…

image

указываем наш HEX файл кнопкой S19 or HEX… и жмем Generate…

image

получаем dfu файл.

Собственно и все готово.

Загрузка в контроллер…

Подключаем нашу подопытную плату с уже загруженным botloader-ом к USB, предварительно установив перемычки в режим DFU Mode.

image

Можно проконтролировать появлением STM Device in DFU Mode в списке устройст…
запускаем «загружалку».

image
указываем ей наш dfu файл…

image

Жмем Upgrade и наблюдаем результат загрузки… для уверенности, жмем проверку.

image

все удачно… можно запускать…

если ошибка вылезла, значит где-то косяк…

image

например…

Итак, будем считать что все удачно… переключаем перемыку в режим работы приложения

image

и наслаждаемся миганием диодика…

Уффф. Столько букоффф. Устал копипастить картинки :-)

Все, спасибо за внимание…
Поделиться публикацией
Комментарии 17
    0
    А такое число звездочек и приведений типа типично для embedded? И еще вопрос: выравнивание там чем-то гарантируется или это счастливое совпадение звезд?
      0
      Для HAL это «нормально», там вообще код в стиле Java EE, если выкинуть лишнее — внезапно становится в три-четыре раза компактнее. Cortex M3 выравнивание не критично.
      +1
      Прикрепить исходники религия не позволяет?
        +1
        Внимательно смотрим и дивимся, что флеш вплоть до адреса 0x0800C000 закрыт для записи и записываем этот адрес, он нам понадобится…

        странно, у меня начало основной программы в таком же камне 0x08006000

        а ведь 0xC000 это 48 кбайт. То есть, если я не ошибаюсь, вы отдали для загрузчика 49 кб из 64 кб (ведь именно столько доступно в stm32f103c8 на голубой таблетке?). А основной программе осталось 64-48 = 16 кб. Как то прискорбно вышло.
          0
          А основной программе осталось 64-48 = 16 кб

          Это если не знать фокуса этой серии. А фокус в том, что флешка там, где она должна закончиться, не заканчивается. Контроллеры серии F103C8 точно имеют (по крайней мере все те, с которыми я работал) 128 кБ флеша (хотя по спеке — всего 64). Такой же фокус у меня прошел на L152C8, по крайней мере приложение из региона ~70-80k вполне себе успешно работает.
          Внешний пруфлинк вот тут.
            +2
            Это если не знать фокуса этой серии. А фокус в том, что флешка там, где она должна закончиться, не заканчивается.

            Да этот фокус почти все знают. Но в продакшене я бы его применять не стал. Ибо STMicroelectronics могут расстроится и следующие 10000 (например) микроконтроллеров поставить вам уже реально на 64 кбайта. И вы ни чего им не предъявите)
              –1
              В кейле этот камень определяется, как 128кб флэша.
                +4
                128кб это камень СВ. С8 имеет 64кб. Те страницы, что за 64кб, их работа не гарантируется, ST их не тестит.
                  0
                  del
                0
                Чет не понял, зачем все это нужно. В чем профит, использования dfu?
                  0

                  Я честно говоря тоже не понял, но предположу, что профит в том, что для прошивки st-link или uart не нужен. Если в устройстве есть usb, можно через него прошивать. Но 48кб это перебор. И необходимость специальной программы, тоже как-то не серьезно. Можно же эмулировать usb флэшку и прошивку на нее просто копировать. St-link v2.1 по-моему так умеет.

                    0
                    Делаете девайс с USB, но без UART-а (выводы нужны подо что-то другое, например, или просто неудобно их выводить), прошиваете через него. Сейчас это многие используют, например в некоторых контроллерах для коптеров — USB является основным портом, для прошивки и настройки, нет необходимости тыкаться по разъемам UART-ов, которые могут еще и на других GPIO оказаться (не на тех, где ждет загрузчик).
                    0
                    Более мощные, например 4хх серия по дефолту имеют встроенный dfu загрузчик, как по типу уартовского.
                      0
                      s/мощные/новые/, например в f042 dfu есть в системном загрузчике.
                        0
                        есть то он есть, но ведь без шифрования)
                      0
                      полным отсутствием пошаговой инструкции

                      4 слова, полностью описывающие всю линейку STM32.
                        0

                        Области памяти, доступные для чтения/записи/удаления, определяются в файле USB_Device/App/usbd_dfu_if.c — описании дескриптора интерфейса. Пример строки описания:


                        #define FLASH_DESC_STR      "@Internal Flash   /0x08000000/03*016Ka,01*016Kg,01*064Kg,07*128Kg,04*016Kg,01*064Kg,07*128Kg"

                        Подробнее о формировании строки дескриптора можно прочесть в разделе DFU mode interface descriptor документа UM0424.


                        В целом следует понимать, что протокол USB-DFU следует — это именно USB протокол. Он имеет несколько функций (записать блок, считать блок, уничтожить блок, считать состояние, считать статус, очистить статус, отключиться). При взаимодействии по DFU, контроллер должен выполнить какие-то действия, в простейшем случае — это записать данные во флэш или считать их. Но валидация адресов — лежит на плечах разработчика в коде микроконтроллера. ПО с компьютера ничто не мешает сгенерировать запись в область RO (где обычно располагается бутлоадер), и контроллер должен это проверить и вернуть ошибку. Для того чтобы демо-утилита от STM знала, куда она может писать, и что может читать — и передается эта строка.

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

                        Самое читаемое