Великое множество обнаруженных в сети bootloader-ов, иногда весьма занятных, к сожалению «заточены» под какой-либо конкретный кристалл.
Предлагаемый материал содержит процедуру использования пакета CubeMX, «загружалки» DfuSeDemo и утилиты подготовки прошивки Dfu file manager, т. е. Мы абстрагируем наши «хотелки» от железки, да простят меня гуру макроассемблера и даташита.
Готовим окружение…
Нам необходимы собственно сам CubeMX, загружалка DfuSeDemo+Dfu file manager, лежат в одном пекете, STM32 ST-LINK Utility, все изыскиваем на сайте STMicroelectronics совершенно бесплатно.
Наша подопытная жлезка с чипом STM32F103C8T6 от дядюшки Ляо
![image](https://habrastorage.org/webt/nq/yw/1b/nqyw1bqocxo7dzc20k8glc6rayq.jpeg)
и программатор ST-Link оттуда же.
![image](https://habrastorage.org/webt/8d/fs/by/8dfsbyggtjuxprlxqzxv0ro8d9c.jpeg)
Ну и ваша любимая IDE, в данном конкретном изложении мы используем KEIL, настройки компиляции в других IDE не очень отличаются.
Итак, поехали…
Запускаем CubeMX и выбираем свой кристалл…
![image](https://habrastorage.org/webt/k2/et/79/k2et79gnu3tsvskudvnihrwil4o.png)
Отмечаем свои хотелки…
![image](https://habrastorage.org/webt/q5/-j/sm/q5-jsmnqmyjquwfoghdjgl6ilrm.png)
В данной конкретной задаче активируем устройство USB>Device FS и соответсвенно USB Device> DownLoad Update Firmware class, и незабываем RCC>High Speed Clock>Cristal/Ceramic Resonator тот что на борту платы.
Далее необходимо выбрать переключалку режима bootloader-a, в данном примере просто задействуем имеющуюся перемычку boot1.
![image](https://habrastorage.org/webt/xw/v9/5v/xwv95v8fai5zheofxav35naskoi.png)
Смотрим схемку и в соответствии с ней boot1 прицеплен к ноге PB2, вот ее и задействуем в режиме GPIO_Input.
Готово, активируем закладку Clock Configuration и запускаем автомат выбора конфигурации.
![image](https://habrastorage.org/webt/yt/vt/7_/ytvt7_xdyfvcw67_uedouuuuztg.png)
Прыгаем на закладку Cofiguration…
![image](https://habrastorage.org/webt/dm/lj/xu/dmljxuuxuk95xvrptvmphixxji8.png)
Выбираем кнопку GPIO…
![image](https://habrastorage.org/webt/ta/dz/bm/tadzbm6mlwerllhy2skcgz25uh0.png)
И в поле…
![image](https://habrastorage.org/webt/v7/gb/_d/v7gb_di9v41r5-ngziilq3ly2ka.png)
пишем пользовательскую метку, пусть это будет boot1.
Далее настраиваем проект…
![image](https://habrastorage.org/webt/ka/nn/-p/kann-pg6u8o6rhn9mbmfm9in-58.png)
Выбираем Project > Setting…
![image](https://habrastorage.org/webt/eq/hj/dv/eqhjdvvqax69j0ewkkx9pmopqq8.png)
Выбираем и заполняем….
![image](https://habrastorage.org/webt/p7/yz/9j/p7yz9jg9b6tkud4okdsafgmb4oa.png)
Соответсвенно выбираем для какого IDE нам Cub сгенерит проект, в нашем случае, MDK-ARM V5.
Закладку Code Generator в данном воплощении оставим без изменений…
![image](https://habrastorage.org/webt/da/pk/dq/dapkdqvethk72rx_i5a13dfmohu.png)
Ну собственно и все, запускаем генерацию проекта Project>Generate Code
![image](https://habrastorage.org/webt/fm/04/gl/fm04gltc6pw8yoggmmtffch7y3y.png)
По окончании Cub предложит сразу запустить вашу IDE… как поступать выбирать вам.
![image](https://habrastorage.org/webt/xw/-a/il/xw-ailwvtsxvmeutr6sm0zhatee.png)
![image](https://habrastorage.org/webt/py/jc/ih/pyjcihn0clelk06mt4hqpb_hlg0.png)
Запускаем компиляцию и сборку и загрузку в кристалл… F7, F8…
![image](https://habrastorage.org/webt/at/ty/lq/attylqbozxzp3zdjgic6om1wbic.png)
Конечный итог…
Переключаем пины на нашей плате в режим работы и подключаем USB кабель…
![image](https://habrastorage.org/webt/v2/ai/0l/v2ai0l19btc_rcovg_wrmn9mmqe.jpeg)
![image](https://habrastorage.org/webt/2k/ef/na/2kefnaowe9tvdhxau8cnqirl4gi.png)
Открываем в Windows панель управления> система> диспечер устройтв> USB контроллер. И смотрим список устройств, Windows немого пошуршит и установит драйвер STM Device in DFU Mode (если он уже не стоял).
Итак, драйвер встал и определился, запускаем «загружалку» DfuSeDemo…
![image](https://habrastorage.org/webt/zx/nh/tk/zxnhtkd8ex8vjmvrh0n6kppvhgu.png)
Смотрим что у нас поймалось DFU Device и дажды кликаем в поле Select Target …
![image](https://habrastorage.org/webt/j1/yq/tf/j1yqtfbrkhdtqicc_ej9ksvq43w.png)
Внимательно смотрим и дивимся, что флеш вплоть до адреса 0x0800C000 закрыт для записи и записываем этот адрес, он нам понадобится…
К слову, пробовал на STM32F407VE, там память открыта для записи с 0x08000000 т. е. С самого начала… почему, в нашем варианте не так, неясно, да и не копал, где то зарыто, но явно не прописано, не есть комильфо, потому что большой кусок пропадает безхозно… может кто и подскажет где копать…
Итак, «стрижка только начата»…
Нам понадобится только два файла исходников…
![image](https://habrastorage.org/webt/mf/xi/qc/mfxiqcukfcwady5b5g1ls0o0i9w.png)
Открываем их в 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](https://habrastorage.org/webt/5q/o3/6c/5qo36cv8ecp_q2973u_oxtrfgw8.png)
Меняем адрес начала программы…
![image](https://habrastorage.org/webt/8j/7a/qs/8j7aqss7abvo0utxbynevhiszt4.png)
Говорим чтобы генерировал HEX файл
![image](https://habrastorage.org/webt/mf/hf/vi/mfhfvi_opbxbpe2bniwmkoapuna.png)
и меняем адрес таблицы векторов…
собираем программу F7…
полученный HEX преобразуем в dfo файл утилитой Dfu file manager…
![image](https://habrastorage.org/webt/k6/ua/z7/k6uaz74t-ocb12judxi3oqays6y.png)
указываем наш HEX файл кнопкой S19 or HEX… и жмем Generate…
![image](https://habrastorage.org/webt/ae/sg/nq/aesgnqotz_ne56nocjjuthhsu5s.png)
получаем dfu файл.
Собственно и все готово.
Загрузка в контроллер…
Подключаем нашу подопытную плату с уже загруженным botloader-ом к USB, предварительно установив перемычки в режим DFU Mode.
![image](https://habrastorage.org/webt/v2/ai/0l/v2ai0l19btc_rcovg_wrmn9mmqe.jpeg)
Можно проконтролировать появлением STM Device in DFU Mode в списке устройст…
запускаем «загружалку».
![image](https://habrastorage.org/webt/xf/z7/dt/xfz7dt2p8btzwldyu1lrzb10k54.png)
указываем ей наш dfu файл…
![image](https://habrastorage.org/webt/yp/qb/6z/ypqb6zl50cbr_dul2laojb59cci.png)
Жмем Upgrade и наблюдаем результат загрузки… для уверенности, жмем проверку.
![image](https://habrastorage.org/webt/i2/5v/k-/i25vk-h-xa7twolptnd79ohtlou.png)
все удачно… можно запускать…
если ошибка вылезла, значит где-то косяк…
![image](https://habrastorage.org/webt/gs/ns/3v/gsns3vvlgr4g1hphtifrmofraua.png)
например…
Итак, будем считать что все удачно… переключаем перемыку в режим работы приложения
![image](https://habrastorage.org/webt/9-/yi/07/9-yi07qksvzsdb3qp9eycnzt0yy.jpeg)
и наслаждаемся миганием диодика…
…
Уффф. Столько букоффф. Устал копипастить картинки :-)
Все, спасибо за внимание…
Комментарии (17)
dernuss
07.12.2018 20:59+1Внимательно смотрим и дивимся, что флеш вплоть до адреса 0x0800C000 закрыт для записи и записываем этот адрес, он нам понадобится…
странно, у меня начало основной программы в таком же камне 0x08006000
а ведь 0xC000 это 48 кбайт. То есть, если я не ошибаюсь, вы отдали для загрузчика 49 кб из 64 кб (ведь именно столько доступно в stm32f103c8 на голубой таблетке?). А основной программе осталось 64-48 = 16 кб. Как то прискорбно вышло.roma_turkin
07.12.2018 21:33А основной программе осталось 64-48 = 16 кб
Это если не знать фокуса этой серии. А фокус в том, что флешка там, где она должна закончиться, не заканчивается. Контроллеры серии F103C8 точно имеют (по крайней мере все те, с которыми я работал) 128 кБ флеша (хотя по спеке — всего 64). Такой же фокус у меня прошел на L152C8, по крайней мере приложение из региона ~70-80k вполне себе успешно работает.
Внешний пруфлинк вот тут.dernuss
07.12.2018 22:26+2Это если не знать фокуса этой серии. А фокус в том, что флешка там, где она должна закончиться, не заканчивается.
Да этот фокус почти все знают. Но в продакшене я бы его применять не стал. Ибо STMicroelectronics могут расстроится и следующие 10000 (например) микроконтроллеров поставить вам уже реально на 64 кбайта. И вы ни чего им не предъявите)
Ksiw
08.12.2018 10:43+4128кб это камень СВ. С8 имеет 64кб. Те страницы, что за 64кб, их работа не гарантируется, ST их не тестит.
Ksiw
07.12.2018 21:18Чет не понял, зачем все это нужно. В чем профит, использования dfu?
Sergey78
07.12.2018 23:06Я честно говоря тоже не понял, но предположу, что профит в том, что для прошивки st-link или uart не нужен. Если в устройстве есть usb, можно через него прошивать. Но 48кб это перебор. И необходимость специальной программы, тоже как-то не серьезно. Можно же эмулировать usb флэшку и прошивку на нее просто копировать. St-link v2.1 по-моему так умеет.
vasimv
08.12.2018 17:47Делаете девайс с USB, но без UART-а (выводы нужны подо что-то другое, например, или просто неудобно их выводить), прошиваете через него. Сейчас это многие используют, например в некоторых контроллерах для коптеров — USB является основным портом, для прошивки и настройки, нет необходимости тыкаться по разъемам UART-ов, которые могут еще и на других GPIO оказаться (не на тех, где ждет загрузчик).
VioletGiraffe
08.12.2018 18:03полным отсутствием пошаговой инструкции
4 слова, полностью описывающие всю линейку STM32.
r1000ru
09.12.2018 12:50Области памяти, доступные для чтения/записи/удаления, определяются в файле
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 знала, куда она может писать, и что может читать — и передается эта строка.
Ryppka
А такое число звездочек и приведений типа типично для embedded? И еще вопрос: выравнивание там чем-то гарантируется или это счастливое совпадение звезд?
rstepanov
Для HAL это «нормально», там вообще код в стиле Java EE, если выкинуть лишнее — внезапно становится в три-четыре раза компактнее. Cortex M3 выравнивание не критично.