В статье я хотел бы описать шаги на пути к написанию прошивки для микроконтроллеров stm32 без использования специальных сред разработки типа keil, eclipse и тому подобных. Я опишу подготовку прошивки с самых основ, начиная с написания загрузчика на ассемблере, скрипта для линкера и заканчивая основной программы на C. В коде на C буду использовать заголовочные файлы из CMSIS. Редактор кода может быть любым на ваш вкус, vim, emacs, блокнот, все что угодно. Для сборки проекта буду использовать утилиту make. Итак, начнем!
Почему так сурово, спросите вы. Во-первых, чтобы что-то хорошо освоить, необходимо начинать с основ. Я не хочу, чтобы мой читатель бездумно щелкал клавишами клавиатуры набирая текст очередной супер-программы для устройства, не понимая, как работает устройство. Stm32 гораздо более сложный микроконтроллер по сравнению, например с atmega8 — atmega328 (микроконтроллером, установленным на самой популярной плате серии arduino). Во-вторых, я люблю сам разбираться в любом деле с нуля, и можно сказать, данная статья — это заметки для меня в будущем, чтобы открыть и вспомнить некоторые нюансы.
Да, я забыл еще сказать, что разработку буду вести под Linux. Подойдет любой дистрибутив, например, у меня это Arch Linux. Для ubuntu процесс установки необходимых утилит я постараюсь описать в следующих частях. Можете попробовать Windows, MacOS, но для этого вам самим придется разобраться, как установить необходимые утилиты для компиляции и прошивки.
Первое, что вам нужно сделать, это приобрести плату для разработки на основе контроллера stm32f103. У меня это blue pill:
Еще одна вещь, необходимая для старта, это программатор st-link:
Плату blue pill и программатор я приобрел на aliexpress, заплатив 200 руб. за все вместе.
Второе, что необходимо сделать, это скачать набор для компиляции кода под arm GNU GCC.
Для arch linux необходимо поставить пакет gcc-arm-none-eabi:
yaourt -Syy arm-none-eabi-gcc
Далее нам понадобится утилита st-link для работы с одноименным программатором st-link2:
yaourt -Syy stlink
Теперь давайте попробуем подключить нашу плату к компьютеру через программатор.
Соединяем программатор с платой blue pill в таком порядке:
- Подключить к пину GND (ground — земля, пина два, возьмите любой, например, 4-й) на программаторе провод (желательно следовать некоторым стандартам, для земли используйте черный или синий) и подключите к пину на плате подписанному GND;
- Подключить пин SWCLK (clock — синхронизация) на программаторе к пину SWCLK на плате;
- Подключить пин SWDIO (IO — ввод/ввод) на прогамматоре к пину SWIO на плате;
- И наконец, пин 3,3V на программаторе соедините с пином 3,3V на плате.
Пока все очень просто. Теперь подключаем программатор в USB порт компьютера, открываем терминал и проверяем, что устройство успешно определилось в системе:
dmesg
При этом на самой плате загорится два диода, один красный должен гореть постоянно, что сигнализирует о том, что питание подается, второй зеленый, должен мигать. Это работает прошивка по-умолчанию.
Теперь давайте проверим характеристики нашей демо-платы. Для этого в терминале запускаем команду st-info из установленного до этого пакета stlink:
На выбор можем посмотреть:
--version — текущая версии утилиты st-info
--flash — выведет информацию о размере flash-памяти программ микроконтроллера, в моем случае это 0x10000 (65536 байт)
--sram — объем статической памяти — 0x5000 (4096 байт)
--descr — описание — F1 Medium-density device
--pagesize — размер страницы памяти — 0x400 (256 байт)
--hla-serial — "x30x30x30x30x30x30x30x30x30x30x30x31"
--probe — Found 1 stlink programmers
serial: 303030303030303030303031
openocd: "x30x30x30x30x30x30x30x30x30x30x30x31"
flash: 65536 (pagesize: 1024)
sram: 20480
chipid: 0x0410
descr: F1 Medium-density device
Из важного для нас — размер flash-памяти и размер статической памяти, а также стоит запомнить что у нас устройство Medium-density.
Не следует начинать разработку без документации под рукой. Во-первых следует скачать с официального сайта Reference Manual. В нем полное описание всей периферии, регистров периферии микроконтроллера. Во-вторых, скачиваем Programmer Manual по той же ссылке. В нем узнаете о микропроцессоре семейства контроллеров STM32F10xxx/20xxx/21xxx/L1xxxx Cortex-M3, его архитектуре, наборе команд.
Далее разберем, с чего вообще начинается исполнение программы на микроконтроллере.
- Наш микроконтроллер stm32f103c8 сразу после включения начинает считывать по адресу 0x08000000 (для удобства чтения я буду делить тетрады пробелом — 0x0800 0000) значение для регистра SP. SP (Stack pointer) — регистр указателя стека (стр. 15 Programmer Manual). Стек начинается с конца доступной RAM-памяти и растет “вверх”;
- По адресу 0x0800 0004 считывает значение в регистр PC — Program counter. Это значение — адрес точки входа в нашу основную программу, другими словами по адресу 0x0800 0004 flash должен лежать адрес C — функции main(), определенной нами далее;
- Микроконтроллер начинает выполнение программы.
Чтобы вычислить начальное расположение стека (значение для SP регистра), обратимся к мануалу Reference Manual на стр. 65. Там указано, что RAM начинается с адреса 0x2000 0000. Ранее мы определили, что у микроконтроллера 4096 байт:
0x2000 0000 + 0x5000 = 0x2000 5000
То есть по адресу 0x0800 0000 мы должны поместить значение 0x2000 5000.
По адресу 0x0800 0004 мы должны положить указатель на начало нашей программы. Каждый указатель имеет размер 4 байта, значит следующий адрес за 0x0800 0004 во flash памяти будет 0x0800 0004 + 4 = 0x0800 0008. Это значение и необходимо поместить по адресу 0x0800 0004.
Так будет выглядеть начальный участок нашей прошивки:
+-------------+-------------+
| Адрес flash | Значение
+-------------+-------------+
| 0x0800 0000 | 0x2000 5000 |
| 0x0800 0004 | 0x0800 0008 |
+-------------+-------------+
Теперь об одной особенности микроконтроллеров stm32. Дело в том, что формат команд для stm32 должен быть в Thumb представлении вместо стандартного ARM. Это значит, что при указании указателей мы должны прибавлять 1. Запомните это правило.
Хватит теории, пора переходить к практике. Надеюсь, вы еще не спите. Открывайте ваш любимый редактор кода, будем писать начальный файл для запуска нашего контроллера. Мы начнем с startup файла и он будет написан на ассемблере. Это будет единственный раз, когда я заставляю вас писать на скучном ассемблере, зато вы начнете понимать и “чувствовать” устройство изнутри.
Пишем в самом начале:
@stm32f103
Это комментарий на языке ассемблера, каждый комментарий начинается с символа @.
Далее указываем директивы ассемблеру
.syntax unified
@тип команд для stm32 - Thumb!
.thumb
@семейство процессора микроконтроллера cortex-m3
@(в этом можно убедиться из мануала)
.cpu cortex-m3
И далее коротенькая bootstrap-программа:
@указатель на вершину стека. Пишем без пробелов!
@.equ директива ассемблера это почти
@тоже что и define в C или на худой конец
@думайте, что это обычное присваивание переменной
.equ StackPointer 0x20005000
@.word - указываем, что здесь машинное слово - 4 байта
@по сути отсюда (0x0800 0000) процессор
@начинает свою работу после включения
.word StackPointer
@”кладем” указатель на начало основной программы.
@Reset в данном случае - метка, адрес точки входа.
@не забываем о том, что у нас набор команд Thumb,
@поэтому к указателю прибавляем единицу
.word Reset + 1
@метка Reset. Здесь мы встречаем первую, настоящую и
@единственную команду, которая нам понадобится на
@протяжении всего руководства. Это команда B -
@безусловный переход в системе команд ARM,
@аналог JMP в ассемблере для x86, или простыми
@словами goto в языках более высокого уровня.
@Аргумент команды B - это адрес безусловного перехода, в нашем случае мы пока
@указываем метку Reset, тем самым заводим процессор в бесконечный цикл.
Reset: B Reset
Программу целиком вы можете скачать по ссылке https://bit.ly/2rc7bcf
Сохраните ее под названием bootstrap.s.
А теперь давайте скомпилируем и прошьем нашу плату.
Прежде всего я покажу, как скачать прошивку по-умолчанию с вашей платы, ту, которая мигает светодиодом. Вдруг когда-нибудь пригодится.
Снова вставляем программатор с подключенной платой в usb и запускаем в терминале Linux команду:
st-flash read ./default.bin 0x08000000 0x10000
Здесь мы указываем, что хотим прочитать в файл default.bin flash-память начиная с адреса 0x08000000 и размером 0x10000 (64K), то есть всю flash-память.
st-flash — утилита для работы с прошивкой микроконтроллера, полное описание ее читайте в терминале: st-flash --help
.
После этого проверим, что прошивка корректно считалась. Загрузим ее вновь, перезаписывая старую.
st-flash write ./default.bin 0x08000000
Что означает записать default.bin в flash память контроллера начиная с адреса 0x08000000.
Выньте и снова вставьте программатор, на плате зеленый диод должен как и раньше мигать.
Теперь давайте скомпилируем нашу самописную прошивку. В терминале в той же директории, что и сохранили запустите:
arm-none-eabi-as -o bootstrap.o bootstrap.s
Здесь мы компилируем наш исходный файл в объектный код. Это еще не готовая прошивка, годная для заливки в микроконтроллер. Нам необходимо еще “указать” куда, по каким адресам размещать нашу программу. Этим занимается компоновщик. Мы воспользуемся самым популярным компоновщиком LD, который входит в поставку пакета arm-none-eabi-gcc. Подробнее о компоновщике и описание скрипта для компоновщика ld я расскажу в следующей части, когда мы перейдем к автоматической сборке нашей супер-простой прошивки. А пока просто скачайте этот маленький файл stm32f103.ld https://bit.ly/2HXIydu, и выполните команду:
arm-none-eabi-ld -o main.elf -T stm32f103.ld bootstrap.o
Этой командой мы компонуем наш объектный файл с помощью скрипта stm32f103.ld, на выходе получаем elf файл.
Чтобы окончательно подготовить исполнимый elf файл к прошиванию, выполним последнюю команду:
arm-none-eabi-objcopy main.elf main.bin -O binary
Здесь мы преобразуем elf файл в чистый бинарный формат, пригодный для заливки в нашу плату.
Итак, наша первая программа для контроллера stm32 готова! Прошиваем!
st-flash write ./main.bin 0x08000000
Поздравляю! Теперь микроконтроллер обречен на вечное выполнение безусловного перехода. До следующей встречи!
Автор: Шалаев Андрей Николаевич