24. STM32. Програмування STM32F103. Bootloader


30.01.2017

У цій статті ми розглянемо що таке Bootloader, навіщо і коли потрібен власний Bootloader, приклад як написати власний Bootloader, приклад як адаптувати прошивку для використання з Bootloader-ом.

Що таке Bootloader?

Bootloader (загрузчик) - це програма яка знаходиться у пам`яті мікроконтролера. Основна функція загрузчика - записати програму (прошивку) у флеш пам`ять мікроконтролера. Тобто, прошивка мікроконтролера. Використовується коли треба дати можливість оновити прошивку кінцевому користувачеві приладу без використання програматора.

Первинний Bootloader

У STM32 є Bootloader який зашитий у пам`ять мікроконтролера при виробництві. Знаходиться він у системній пам`яті мікроконтролера. Його неможливо затерти чи підмінити. Коли ми прошиваємо мікроконтролер через UART, прошивкою займається саме первинний Bootloader.

Вторинний Bootloader

Навіщо мати свій власний Bootloader? Коли виникає питання по організації оновлення прошивки у готових пристроях. Ви, як розробник, можете підключити програматор чи USB-UART перехідник і перепрошити мікроконтролр без проблем. Та що робити користувачам у яких програматора і немає бути? Треба знайти більш зручний шлях. В таких випадках і треба використовувати свій Bootloader, який зможе оновити прошивку без застосування програматора. Наприклад, у вашому пристрої використовується Bluetooth модуль, у цьому випадку можна оновити прошивку, наприклад, з мобільного телефону. Якщо використовується WiFi модуль, можна взагалі оновити прошивку через Інтернет. Якщо Ваш пристрій використовує SD-картку, можна записати на неї нову прошивку і Ваш Bootloader зашиє її у мікроконтролер. Деякі мікроконтролери мають USB, тому логічно використати USB для оновлення прошивки. Якщо є необхідність захистити комерційний проект і зберегти прошивку від розповсюдження, але в той же час дозволити вільно обновляти її користувачам тільки Вашого пристрою, можна шифрувати прошивку. Написаний Вами Bootloader буде розшифровувати її у момент прошивки. Захистивши пам`ять мікроконтролера та виконавши інші дії пов`язані з безпекою, можна зробити неможливим копіювання прошивки. Найбільш універсальним для вирішення таких задач, на мою думку, є USB інтерфейс. У цій статті ми використаємо саме USB для демонстрації роботи Bootloader-а. Деякі мікроконтролери мають вбудований USB DFU (Device Firmware Upgrade). Для прошивки через USB треба встановити драйвери та використовувати для прошивки спеціальне програмне забезпечення. В мікроконтролері STM32F103, який я використовую для прикладів, USB DFU немає, але є інтерфейс USB. Для прикладу ми напишемо власний Bootloader, який буде оновляти прошивку мікроконтролера саме через цей інтерфейс. У якості підопічного буде, вже знайомий модуль з мікроконтролером STM32F103.

Свій Bootloader. Як це працює

Оскільки ми не можемо перепрошити первинний Bootloader мікроконтролерів STM, наш Bootloader буде знаходитись у пам`яті програм. Фактично Bootloader - це Ваша програма яка буде опікуватися процесом прошивки. Тобто, при включенні мікроконтролера буде запускатися саме Bootloader, як звичайна програма. Звісно, с самого початку Bootloader має з’ясувати що йому робити. Заливати нову програму чи передати управління вже залитій програмі. Це може бути перевірка наявності прошивки на носієві, або перевірка стану якоїсь кнопки чи перемички. Другий метод мені подобається дужче бо так можна уникнути непотрібних затримок при старті. Натиснута кнопка при включенні приладу - ідемо оновлятися. Не натиснута - працюємо як завжди. Питання звідки Bootloader буде забирати прошивку і починаючи з якої адреси записувати у Flash мікроконтролера, це цілком ваші питання, як розробника. І ми розглянемо ці питання на прикладі. В залежності від реалізації конкретного приладу і наявності у нього певної периферії, прошивку можна передавати у мікроконтролер через інтерфейс UART, IIC, SPI, USB, тощо. Звісно, Bootloader буде займати певну частину пам`яті і для основної програми залишиться решта пам`яті. Ви маєте чітко знати скільки займає Ваш Bootloader і починаючи з якої адреси у Flash буде знаходитися Ваша основна програма. Стандартно, для STM32F103, програми знаходяться починаючи з адреси 0x08000000. Саме починаючи з цієї адреси буде розташований Bootloader. Якщо наш Bootloader буде займати 10Кb. Тоді головну прошивку треба розташувати починаючи з адреси 0x08002800. Тобто, з відступом у 10Кb. При цьому відступ має бути кратний розміру сторінки Flash. Згадаємо як організована Flash пам`ять мікроконтролера. Треба чітко пам`ятати, що Flash поділена на сторінки. У нашому контролері (STM32F103) розмір сторінки 1Kб. Адреса, починаючи з якої буде розташована основна прошивка, має вказувати на початок не зайнятої Bootloader-ом сторінки. Наприклад, якщо Bootloader буде розміром 10300 байт. Він займе цілком перші 10 сторінок (10240 байт) і 60 байт одинадцятої сторінки. Ми маємо розташувати основну прошивку починаючи з 12-тої сторінки (адреса: 0x08003000). Чому ми не можемо зайняти частину 11-ої сторінки? Вона ж майже вся вільна! Перед записом у флеш треба спочатку стирати сторінку. При цьому стирається сторінка цілком. Не має можливості стерти частину сторінки. Про це йшлося у статті STM32. Програмування STM32F103. Flash. Якщо ми вкажемо адресу, де має починатися основна прошивка з середини 11-ої сторінки, тоді при оновленні прошивки Bootloader-у потрібно буде перед записом спочатку стерти сторінку 11. Це саме сторінка де знаходиться його останні байти. Він зітре сторінку цілком. Таким чином він може "стрильнути собі у ногу". Тому основна прошивка має займати сторінки пам`яті які не зайняти Bootloader-ом.

Bootloader у режимі USB Mass Storage

Мене приваблює Bootloader у режимі USB Mass Storage. При підключенні до USB комп’ютера, мікроконтролер має бути для операційної системи як USB Mass Storage Device. Тобто, як невеличка флешка у декілька кілобайт. Для оновлення прошивки достатньо записати на неї новий файл. Це, на мою думку, самий прийнятний метод оновлення прошивки для кінцевого користувача. По перше він не потребує спеціалізованого програмного забезпечення, по друге має працювати на любій операційній системі де існує поняття файлової системи. Це був би ідеальний варіант. Але це у теорії все красиво, на практиці все трохи складніше. Спочатку я знайшов таку реалізацію: http://blog.myelectronics.com.ua/stm32-usb-mass-storage-bootloader/ Але для нашого мікроконтролера таке рішення не прийнятне. У цьому рішенні Flash пам`ять мікроконтролера поділена на декілька частин. А саме на частини де лежить Bootloader, де знаходиться основна прошивка, та область пам`яті відведена під MASS STORAGE. Прошивка спочатку записується на MASS STORAGE диск. А потім копіюється у потрібну область. Тобто прошивка має бути менше ніж половина пам`яті мікроконтролера. Це дуже мало, коли у нас усього 64Кб.

Поясню. У нас усього 64Кб, нехай 10Кб займе Bootloader, лишається 54Кб. Половину віддамо під Mass Storage, це буде 27Кб. З цього об`єму FAT12 відкусить 20Кб, лишаться 7Кб під файл прошивки. Тобто на Mass Storage не влізе файл більше за 7Кб. Якщо оптимізувати розподіл пам’яті і віддати під Mass Storage 38Кб, тоді під програми залишиться 16 Кб. Це для нас теж не прийнятно. Тому таке рішення для нашого мікроконтролера, з невеличким об`ємом Flash-а, не підходить. Але подякуємо автору! Саме фрагменти цього проекту я використав для створення прикладу Bootloader-а описаного у цій статті. Подальші пошуки привели мене до Bootloader-а описаного у статті http://easyelectronics.ru/proshivka-arm-cortex-m3-na-primere-stm32-i-lpc1300.html Шикарний задум, але реалізація цілком не прийнятна. Контролер дійсно видно як MASS STORAGE диск, на нього начебто можна залити нову прошивку, але коректно це відбувається лише коли файл копіювати програмою FAR. І тільки FAR! Дивно, але просте копіювання файлу відбувається по різному якщо це робити різними файл-менеджерами. Операційна система Ubuntu взагалі не змогла змонтувати пристрій. Поправити щось у цьому Bootloader-і не можна, тому ця реалізація теж відкинута. Довелося писати свій Bootloader. Чому саме USB Mass Storage? Скажімо, я роблю не комерційний проект і хочу щоб для оновлення прошивки кінцевим користувачам не потрібно було шукати специфічний софт і драйвери. Для роботи з Mass Storage пристроями операційні системи використовують стандартні драйвери, тому з драйверами проблем взагалі немає. Оновлення прошивки може відбуватися на різних операційних системах тому бажано щоб програми для оновлення прошивки взагалі не треба було, або використовувались стандартні рішення. У цьому випадку просте копіювання файлу - найбільш гарне рішення. Забігаючи на перед скажу, що не все так просто. Мені поки що не вдалося реалізувати цей задум у ідеальному вигляді. Тому бажано щоб процедура заливки прошивки була більш-менш стандартною і, як я сказав, можна було обійтися стандартними командами або стандартним програмним забезпеченням. Продовжуємо розбиратися...

USB Mass Storage приклад

Перед тим, як робити Bootloader у режимі USB Mass Storage спочатку реалізуємо роботу мікроконтролера у режимі USB Mass Storage, щоб зрозуміти як це працює. Змусимо контролер працювати як маленька флешка. Візьмемо приклад з бібліотеки STM32_USB-FS-Device_Lib_V4.0.0. Я писав про USB і використання STM32_USB-FS-Device_Lib. Посилання щоб скачати приклад USB Mass Storage: https://github.com/avislab/STM32F103/tree/master/Example_USB_Mass_Storage. У цьому прикладі під дані відведено 54Кб. Перші 10Кб відведено під програму (у подальшому це буде наш bootloader). Дивись налаштування у файлі mass_mal.h:

#define FLASH_DISK_START_ADDRESS 0x08002800 /* Flash start address */
#define FLASH_DISK_SIZE 55296
#define FLASH_PAGE_SIZE 1024 /* 1K per page */
Запрограмуємо мікроконтролер і підключимо його USB шнуром до комп`ютера. Комп’ютер має побачити прилад і використати для спілкування з ним стандартні драйвери. При цьому Windows змонтує його як диск, при переході на нього, Windows скаже що диск не відформатований. Це нормально. Форматуйте його. Тепер на нього можна писати файли. Правда, невеличкі. Давайте подивимось який об’єм нам доступний. 54Кб? Ні, менше! А чому? Тому що на "диску" має розташовуватися таблиця FAT. Тобто, ми ніяк не зможемо записати на нього файл 54Кб.

Давайте покопаємося у коді і розберемось як саме виконується робота з Mass Storage. Виявляється мікроконтролер взагалі нічого не знає про FAT, кластери і таке інше. Він працює лише як посередник - передає данні з USB у Flash, та з Flash у USB. А куди і що писати власно вирішує операційна система. Віддавати 20Кб флеш пам`яті мікроконтролера для FAT - це забагато. А що можна взагалі вигадати для реалізації Bootloader-а у режимі USB Mass Storage? Фактично нам треба виконувати лише одну операцію копіювання одного файлу, можна навіть з фіксованим ім`ям. Можна дурити операційну систему і програмно формувати данні FAT, але фактично не зберігати її. Для одного файлу у корні з фіксованим іменем це цілком реальна ідея яка зекономить пам`ять. При копіюванні файлу на наш USB Mass Storage треба буде лише записати данні у флеш. Але я прикинув скільки прийдеться заглиблюватися у "муляж" FAT12 і скільки вигрібати "граблів", що вирішив відкласти цей задум. Поки що вирішимо задачу більш простим шляхом. Я не знайшов у Інтернеті щоб так хтось робив, тому кажу що це моя власна ідея. Мені приємно так думати, але у мене є підозра що я не перший такий хитрий :)

Головна ідея нашого USB Mass Storage Bootloader

Отже, оскільки операційна система бачить наш пристрій як диск, просто задампимо (стандартним ПЗ) на нього наш bin - файл. Він ляже у флеш в акурат з потрібної нам адреси (у нашому випадку 0x08002800). Тобто, люба утиліта, яка може заливати образ диску на флешку зможе залити нашу прошивку. Ми просто підсунемо програмі замість образу диска нашу прошивку у форматі bin. Так, система не побачить на ньому файлову систему, але нам цього і не треба? Головна задача буде вирішена - прошивку буде оновлено. Чому саме bin - файл а не, скажімо HEX? Саме bin - файл - бінарний файл, який фактично є дампом пам`яті для мікроконтролера. Дамп залити можна стандартною командою dd (під Ubuntu). Під Windows можна використати дуже популярну програму Win32DiskImager для запису образів дисків на Flash-носії. Для перевірки цього метода можна переглянути як саме виглядає програма у флеш пам`яті мікроконтролера за допомогою програматора і ST-LINK Utility. Залийте bin - файл звичайним програматором і описаним мною методом і порівняйте. Результат буде ідентичним!

Команда для заливки на Mass Storage bin-файлу під Ubuntu:


sudo dd if=my.bin of=/dev/sdc
Де: my.bin - файл прошивки в форматі bin /dev/sdc - USB Mass Storage пристрій З`ясувати яке саме ім`я пристрою можна за допомогою команди:

sudo fdisk -l
Я знаю точний розмір тому можна грепнуть по розміру:

sudo fdisk -l | grep 55296
або знайти по ID виробника:

lsusb -d 0483:5720
Під Windows все простіше. Скачуємо і встановлюємо програму Win32DiskImager. Обираємо bin - файл для заливки, диск на який будемо заливати і нажимаємо "Write".

Отже, наш USB Mass Storage, який тепер грає роль Bootloader-а, прошивку вже оновляє. Тепер лишилося доробити USB Mass Storage приклад таким чином, щоб він міг передавати керування залитій програмі. Я це зробив за допомогою кнопки. Коли ми підключаємо мікроконтролер до комп’ютера USB кабелем, і при цьому нога PB1 замкнута на землю, контролер стартує як USB Mass Storage пристрій і можна заливати прошивку. Якщо під час старту PB1 на землю не замкнута, а висить у повітрі, чи підключена до +, мікроконтролер передає курування залитій прошивці. Ось як виглядає код нашого Bootloader-а (доопрацьованого прикладу USB Mass Storage):


#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_flash.h"
#include "stm32f10x_rcc.h"
#include "misc.h"
#include "usb_lib.h"
#include "hw_config.h"
#include "usb_pwr.h"
#include "mass_mal.h"
#define BUTTON_PIN	GPIO_Pin_1
void GoToUserApp(void)
{
	u32 appJumpAddress;
	void (*GoToApp)(void);
	appJumpAddress = *((volatile u32*)(FLASH_DISK_START_ADDRESS + 4));
	GoToApp = (void (*)(void))appJumpAddress;
	SCB->VTOR = FLASH_DISK_START_ADDRESS;
	__set_MSP(*((volatile u32*) FLASH_DISK_START_ADDRESS)); //stack pointer (to RAM) for USER app in this address
	GoToApp();
}
int main(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	/* Initialize Button input PB */
	// Enable PORTB Clock
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	/* Configure the GPIO_BUTTON pin */
	GPIO_InitStructure.GPIO_Pin = BUTTON_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	if (GPIO_ReadInputDataBit(GPIOB, BUTTON_PIN) == Bit_SET) {
		// Go To Application
		GPIO_DeInit(GPIOB);
		GoToUserApp();
	}
	else {
		// Initialize Mass Storage
		Set_System();
		Set_USBClock();
		USB_Interrupts_Config();
		USB_Init();
		while (bDeviceState != CONFIGURED);
		while (1)
		{
		}
	}
}
Цілком проект можна скачати тут: https://github.com/avislab/STM32F103/tree/master/Example_Bootloader

Корекція проектів для роботи з Bootloader-ом

Оскільки наша основна програма тепер буде знаходитись у пам`яті починаючи з адреси 0x08002800, а не 0x08000000, це треба вказати у властивостях проекту у налаштування Linker-а.

Ще треба перенести таблицю векторів переривань. Для цього у перших рядках програми треба добавити наступну функцію:


NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x08002800);

Це все. Тепер компілюємо проект і отримуємо готовий для заливки bin-файл. Приклад проекту можна скачати тут: https://github.com/avislab/STM32F103/tree/master/Example_First_Programm_for_Bootloader

Резюме

Як це виглядає для розробника

  • Заливаємо у мікроконтролер звичайним програматором Bootloader як звичайну програму;
  • Готуємо і компілюємо проект з урахуванням адреси починаючи з якої буде знаходитись основна програма (залежить від розміру Bootloader-а);
  • Готовий bin-файл заливаємо у мікроконтролер як це буде робити кінцевий користувач. Дивись нижче...

Як це виглядає для користувача

Коли користувачеві треба оновити прошивку вашого пристрою він виконує наступні дії:
  • Отримує від розробника новий bin-файл;
  • Підключає пристрій USB кабелем до комп`ютера при цьому утримує нажатою кнопку. Це може бути будь яка кнопка керування вашим пристроєм. Якщо у вашому пристрої не використовуються кнопки, це може бути якийсь перемикач, тощо, що може дати сигнал Bootloader-у що треба перейти у режим оновлення прошивки. У прикладі Bootloader-а треба замкнути PB1 на землю. Bootloader переходить у режим USB Mass Storage, операційна система бачить диск розміром 54Кб;
  • за допомогою програми Win32DiskImager (для Windows) або dd (для Ubuntu) користувач записує bin-файл на цей диск;
  • відключає пристрій від комп’ютера і вмикає. Нова прошивка працює.
Таким чином кінцевому користувачеві не потрібно встановлювати драйвери, а для оновлення прошивки достатньо використати стандартні команди (для Windows - майже стандартне програмне забезпечення). Що більш ніж достатньо для не комерційного продукту. Ви можете модифікувати та оптимізувати цей приклад для вирішення своїх задач. Бажаю успіхів.

Дивись також:

STM32
Коментарі:
Василь говорить:
02.02.2017 22:21
З вихідом після свят !  З новим матеріалом !

Сергій говорить:
23.02.2017 18:08
Дякую за статтю.
У мікроконтролера є два входи BOOT0 та BOOT1 призначення яких - завантажити бутлоадер. BOOT0 завантажує "фабричний" бутлоадер для програмування мікроконтролера через USART1 за допомогою Flash Loader Demonstrator

Чому б нам не використати BOOT1 для свого бутлоадера?

andre говорить:
23.02.2017 20:38
Можна використати любий вхід чи комбінацію входів який Вам зручно.

Сергій говорить:
02.03.2017 19:13
Не можу знайти де задається строка "Avislab STM32-Bootloader USB Device"

andre говорить:
05.03.2017 12:52
Шукайте у файлі USB/src/scsi_data.c

Сергій Слободян говорить:
05.06.2017 20:38
Дуже цікаво, дякую. Маленька пропозиція. Замість кнопки можна подати на порт напругу з ЮСБ входу. Тобто якщо прилад підключили до компа, то бутлоадер перейде в режим обнови прошивки. Якщо ж при старті на ЮСБ роз"ємі не буде напруги, то стартуємо прошивку. Звісно, підходить тільки для пристроїв, які не працюють по ЮСБ в звичайному режимі

Виктор говорить:
19.03.2024 08:05
Прошу прощения, объясните, пожалуйста, почему указатель стека задается как стартовый адрес флеша запускаемого приложения? 

Додати коментар
Code
* - обов'язкові поля

Архіви