"Если дом начинается с двери, то прошивка начинается с загрузчика."
Пролог
В этом тексте написано про то, как я написал загрузчик для российского микроконтроллера MIK32 (K1948BK018).
Определения
Чтобы понять текст надо вспомнить вот эти определения.
1--Прошивка (firmware) - содержимое энергонезависимой памяти электронного
устройства с микроконтроллером. В прошивке всегда есть код, а иногда ещё образ
файловой системы NVRAM, конфиги процессора.
Монолитная прошивка может содержать ещё и загрузчик.
2--Загрузчик - это отдельная прошивка, которая загружает другую прошивку.
Обычно загрузчик стартует сразу после подачи питания перед запуском приложения.
Это чисто системная часть кода.
3--EEPROM - electrically erasable programmable read-only memory.
Это энергонезависимая память, которая зиждется на самом кристалле.
В нашем случае 8kByte.
Что надо из оборудования?
Оборудование |
количество |
Учебно-тренировочная электронная отладочная плата START-MIK32-V1 |
1 |
Кабель USB-A на USB micro |
1 |
Перемычки гнездо-гнездо |
4 |
Переходник с USB на UART на ASIC CP2102 |
1 |
Для того чтобы работать с загрузчиком надо соединить отладочную плату и LapTop PC согласно вот этой схеме.

Что надо из ПО?
Представленное ПО нужно не сколько для разработки сколько для проверки работоспособности загрузчика.
№ |
Название утилиты |
Пояснение |
1 |
Операционная система Windows 10 |
Для исполнения прикладного ПО |
2 |
WinRar |
для распаковки terminal 1.9b |
3 |
Программа terminal 1.9b |
для тестирования и отладки загрузчика |
4 |
Консольная утилита FW-loader.exe |
для нарезания hex файла с прошивкой и отправкой его на устройство |
Постановка задачи
Общая задача такова, что надо обновить прошивку в off-chip SPI памяти при помощи
on-chip MIK32 загрузчиком по UART0. То есть обновить прошивку без программатора. Главная задача любого загрузчика - это дать возможность обновлять боевую flash и ни при
каких обстоятельствах не позволить устройству зависнуть.
Перед разработкой я выдвинул вот такие технические требования к загрузчику:
1--Загрузчик должен помещаться в 8kByte памяти EEPROM программ.
Это обусловлено тем, что в микроконтроллере K1948BK018 просто нет больше on-chip
памяти, чем 8kByte EEPROM.
2--Загрузчик должен уметь прошивать по UART0. Это обусловлено тем, что в плату START-MIK32-V1 вмонтирован переходник с USB на UART на основе отдельного 8-ми битного E8051 совместимого микроконтроллера CH552T. Одновременно с этим, для интерфейса UART существует множество бесплатного диагностического софта для serial портов. Примерами таких программ являются утилиты Terminal 1.9b, Putty, TeraTerm и прочее.
3--Битовая скорость UART0 в загрузчике должна составлять 56000 бит/s. Это самая максимальная (из стандартных) битовая скорость, которую поддерживает заводская
прошивка внутри переходника на основе CH552T.
4--Загрузчик должен перед прыжком в flash настроить трансивер SPIFI в QUAD режим.
5--Загрузчик должен перед прыжком в flash включить кэш в SPIFI трансивере.
6--Желательно, чтобы загрузчик осуществлял прыжок в боевую flash самопроивзольно по истечении определенного timeout (40...70 секунд) c момента подачи электропитания или по таймауту от отсутствия входного трафика в шине UART0.
7--Загрузчик должен записывать прошивку по частям. По возможности перед записью фрагмента в ячейки SPI-Flash проверять контрольную сумму принятого фрагмента данных для памяти.
8--Загрузчик должен быть однопоточной NoRTOS прошивкой. Так можно уменьшить *.bin(арь) и упростить код загрузчика.
9--Желательно, чтобы была UART команда приказывать загрузчику прыгнуть в
приложение и начать исполнять код в SPI-Flash.
10--Желательно, чтобы загрузчик периодически раз в 10...20 секунд посылал в UART0 hello пакеты. Это позволит на стороне клиентского приложения автоматически убедится, что прошивка не зависла.
11--Загрузчик это не просто ещё одна прошивка в репозитории. Для загрузчика нужна инфраструктура и экосистема. В самом простом виде - это консольное Windows приложение (FW_Loader.exe) под Windows 10 PC для нарезания *.hex фйла и отправки фрагментов прошивки по последовательному COM порту.
12--Сам загрузчик записывать программатором по JTAG. На плате START-MIK32-V1 в качестве программатора выступает микросхема CH552T (U4).
Структура загрузчика
Данный состоит из следующих программных компонентов:
1-- Драйвер GPIO.
2-- Драйвер светодиода (LED).
3-- Драйвер SPIFI трансивера.
4-- Драйвер ASIC-а W25Q32JV.
5-- Драйвер UART трансивера.
6-- Программный компонент бинарного протокола передачи данных Trivial Binary Frame Protocol (TBFP).
7-- Простой кооперативный планировщик. По сути - суперцикл.
8-- Программный компонент FIFO.
9-- Программный компонент CRC8 (как дополнение).
LED нужен для того, чтобы показать пользователю, что прошивка загрузчика не зависла.
Тут логика простая. Если LED мигает, значит загрузчик должен отвечать на TBFP пакеты.
Если LED не мигает - значит что-то внутри прошивки заклинило. Это классический HeartBeat LED.
Команды загрузчика
В прошивке загрузчика запущен бинарный протокол обмена данными именуемый
Trivial Binary Frame Protocol (TBFP). Обновление прошивки тоже предполагается через TBFP. Структура пакета для обновления прошивки показана на рисунке.

Это бинарный протокол поверх UART0. В качестве идентификатора полезной нагрузки выступает код 0xFC В диапазоне полезной нагрузки находится заголовок команды: чтение, запись, стирание, относительный адрес внутри микросхемы W25Q32JV, размер.
Номер ASICа в нашем случае должен быть равен единице. Поле данных заполняется только для операции записи.
Ввиду дефицита EEPROM памяти проверка контрольной суммы отключена. Также её можно отключить в флагах внутри заготовка TBFP пакета. В прерывании по UART0 RX происходит запись принятого байта в очередь для протокола TBFP. В прошивке есть конечный автомат, который обрабатывает входящие TBFP пакеты. Синтаксический разбор пакета происходит именно в суперцикле, никак не в прерывании. Прошивка в суперцикле сбрасывает TBFP парсер в ожидание преамбулы, если долгое время (2s) ничего не происходило на шине UART0 RX.
Механизм обновления
Обновление прошивки происходит на UART0 при битовой скорости 56000 бит/c.
Параметры кадра: два стоповых бита, нет проверки четности, один кадр - 8 бит.
Сеанс связи показан на временной диаграмме процесса обновления прошивки. Перед обновлением необходимо отправить TBFP пакет стирания содержимого микросхемы W25Q32JV.

После обновления можно принудительно прыгнуть исполнять код по адресу SPIFI (0x8000_0000) отправив в UART0 пакет A5 C1 01 00 04 00 01 00 00 00 80 E4
Отладка бинарного протокола
Сам протокол TBFP отлажен в составе консольного Windows PC приложения.
На это есть модульные тесты внутри. В частности на чтение и запись по протоколу TBFP. Консольная Win утилита также умеет генерировать тестировочные TBFP пакеты,
чтобы не набирать их вручную.
---------------------------------------------------------
Erase all contents of the SPI memory chip
-->tseg
StoreEraseFrame:
A5C101000800FC000000000000030123
A5C101000800FC 00000000 0000 03 01 23
Insert for the program terminal 1.9b: Erase all SPI memory chip
$A5$C1$01$00$08$00$FC$00$00$00$00$00$00$03$01$23
----------------------------------------------------
Generate a packet to read from the address (tsrg):
Usage: tsrg addr size
-->tsrg 0x00000000 16
Insert for the program terminal 1.9b: read 0x00000000 16 byte
$A5$81$01$00$08$00$FC$00$00$00$00$10$00$01$01$59
-----------------------------------------------------------
Let's try to write something down
Usage: tswg addr size pattern
-->tswg 0x00000000 8 0x77
Insert for the program terminal 1.9b: write 0x77 down at 0x00000000
$A5$C1$01$00$10$00$FC$00$00$00$00$08$00$02$01$77$77$77$77$77$77$77$77$29
-------------------------------
-->tgj 0x80000000
W,[TBFP] GenerateJumpToAddr:0x80000000 Packet
I,[TBFP] N:1,PRE:0xa5,IF:Stdio,RxMem:0047b8e0,RxSz:512,UART:85,SN:0,PrevFlow:0,CurFlow:0,MaxFlow:0,TornCnt:0,Lost:0,
I,[TBFP] PayLoadSize:4 byte
I,[TBFP] JumpFrame: A5C1010004000100000080E4
A5C10100040001 00000080 E4
Insert for the program terminal 1.9b: jump to addr: 0x80000000
$A5$C1$01$00$04$00$01$00$00$00$80$E4
Эти пакеты с символом доллара предназначены для вставки в программу Terminal 1.9b.
Отладка на устройстве
В программе Terminal надо установить паузу между символами. Отладка производилась на значениях в диапазоне 20ms-50ms между байтами. Иначе прошивка может захлебнуться от плотного потока входных байтов.
Как можно заметить, данные в самом деле прописываются и читаются.
Достоинства загрузчика
1--Загрузчиком можно обновлять прошивку без программатора.
2--Когда есть загрузчик, то можно делать DevOps.
Автоматически обновлять прошивки после сборки из репозитория.
Особенности данного загрузчика
1--TBFP - это самописный протокол. Полная спецификация на TBFP протокол находится в разработке. Для обновления прошивки payload_id будет всегда один и тот же: 0xFC.
2--Проверка CRC8 отключена так, как код вычисления CRC8 не поместился в EEPROM память.
3--Так как прошивку пришлось утрамбовывать в 8 kByte получилось так, что
в этом загрузчике можно только полностью стереть весь Flash. И только потом аккуратно писать страницы. Это паллиативное решение. Вот так выглядит пакет инициирующий процесс стирания SPI-Flash памяти:
A5 C1 01 00 08 00 FC 00 00 00 00 00 00 03 01 23
Что можно улучшить?
–Как вы могли заметить, в микроконтроллере K1948BK018 RAM памяти в два раза больше чем ROM памяти. Получается, что можно собрать прошивку более функционального вторичного загрузчика и прописать её прямо в RAM память. При этом первичный EEPROМ загрузчик, напротив уменьшить до функции одной только записи RAM памяти через UART.
Первичный загрузчик из ROM принимает более объёмистый вторичный загрузчик в RAM, прыгает в него, а тот уже творит что хочет. Получится уже не гарвардская, а принстонская архитектура компьютера.
Итог
Удалось составить загрузчик, который обладает минимально необходимой системой команд
для загрузки прошивки в off-chip средствами SPIFI трансивера. Сам загрузчик исполняется из EEPROM. То есть из on-chip EEPROM памяти.
Чтобы соответствовать жестким требованиям по нехватке памяти загрузчик пришлось собрать с ключами -Os -flto. Из-за этого стала невозможной пошаговая отладка.
По мере отчаянного утрамбовывания бинаря следует выявлять те сорцы, которые требуют относительно много в сегменте text. Посмотреть размер прошивки по сегментам можно вот такой binutils командой.
riscv-none-elf-size.exe -Bdt build/*.o
riscv-none-elf-size.exe -Bdt build/*.o | sort
clear && riscv-none-elf-size.exe -Bdt build/*.o | sort
readelf -a start_mik32_v1_eeprom_bootloader_m.elf | grep -i fun | sort -k3
riscv-none-elf-readelf -a start_mik32_v1_eeprom_bootloader_m.elf | grep -i OBJECT | grep -i LOCAL | sort -k3
Тем не менее пошагово отлаживать прошивку можно светодиодом. LED - это, пожалуй, единственный вариант отладки, когда прошивка собрана с жёсточайшей оптимизацией. Даже, если Вы поставите точку останова, то из-за оптимизации курсор окажется в случайном месте. Таким образом пошаговая отладка не поможет. Вот и остаётся ставить себе подсказки при помощи GPIO.
Всю логику протокола TBFP я отладил отдельно в консольной Windows утилите, которая была собрана из тех же самых исходников, что и сама прошивка.
Акронимы
Акроним |
Расшифровка |
FW |
FirmWare |
DFU |
device firmware update |
SPI |
Serial Peripheral Interface |
SPIFI |
Serial Peripheral Interface Flash Interface |
UART |
universal asynchronous receiver-transmitter |
ISR |
Interrupt Service Routine |
KA |
Конечный Aвтомат |
TBFP |
Trivial Binary Frame Protocol |
CRC |
Cyclic redundancy check |
RAM |
Random Access Memory |
RISC-V |
Reduced instruction set computer V |
ASIC |
application-specific integrated circuit |
EEPROM |
Electrically Erasable Programmable Read-Only Memory |
Ссылки
# |
Название |
URL |
0 |
Бинарь загрузчика |
https://github.com/aabzel/Artifacts/tree/main/start_mik32_v1_eeprom_bootloader_m |
1 |
Исходный код EEPROM загрузчика (по NDA) |
--- |
2 |
Атрибуты Хорошего Канального Протокола Передачи Данных |
|
3 |
Атрибуты Хорошего Загрузчика |
|
4 |
NVRAM для микроконтроллеров |
|
5 |
NVRAM из EEPROM |
|
6 |
Микроконтроллер и Bootloader. Описание и принцип работы. |
https://microtechnics.ru/mikrokontroller-i-bootloader-opisanie-i-princip-raboty/ |
7 |
Настройка ToolChain-а Cборки Прошивок для MIK32 (MIK32 + C+ GCC + GNU Make + OpenOCD) |
Контрольные вопросы
1-- Зачем нужен загрузчик во встраиваемых системах? Назовите минимум 3 его функции.
2-- Как загрузчик может обмениваться данными с приложением?
3-- В чем опасность вызова функций загрузчика из приложения?
4-- Как защитить микроконтроллер от загрузки чужеродного кода через загрузчик?
5-- Как загрузчику понять, что загрузчик принял в самом деле прошивку, а не набор случайных циферок с правильной CRC?
6-- Можно ли сделать так, чтобы загрузчик стартовал не с адреса начала Main Flash 0x0800_0000, а например с адреса 0x0806_0000?
7-- Вам прислали прошивки в *.bin файле. Как загрузить и запустить эту прошивку по произвольному отступу в on-chip Nor Flash памяти?
VelocidadAbsurda
Странновато, что 8к оказалось впритык под такое, но, глядя на характеристики данного чипа, упоминающие 16к RAM, напрашивается другой классический путь - первичный загрузчик из ROM принимает более объёмистый вторичный загрузчик в RAM, прыгает в него, а тот уже творит что хочет. Дополнительные плюсы: можно тем же способом загружать и гонять любые сервисные вещи - тестер платы, «читальщик» flash для диагностики сбоев итд. Для уменьшения объёма вторичного загрузчика можно даже переиспользовать фкнкции из ROM (код вашего протокола, к примеру).
aabzel Автор
Спасибо за идею. Я бы до такого никогда не додумался.
aabzel Автор
Получится уже не гарвардская, а принстонская архитектура компьютера.