LINUX.ORG.RU
ФорумTalks

STM32 с нуля (жж)

 , ,


7

2

Я тут в очередной раз пытаюсь освоить программирование микроконтролеров. Захотелось написать такой полу-ЖЖ, полу-туториал (как говорится - хочешь разобраться в чём-то, объясни это другим). Может кто почерпнёт или подскажет чего полезного.

Если интерес будет, буду продолжать.

Итак осваиваем STM32 не как нормальные люди.

Примерный план:

  1. Подключить его к компьютеру и убедиться, что там что-то происходит. Использовать будем st-util и gdb.

  2. Написать простейшую программу на ассемблере, которая в цикле прибавляет регистр, скомпилировать из неё прошивку, залить на плату и пронаблюдать её работу. Использовать будем binutils и st-flash.

  3. Поморгать диодом (на ассемблере же).

  4. Переписать осмысленный код на С (дальше всё на С).

  5. Переписать моргание с использованием таймера, чтобы внести свой вклад в борьбу с глобальным потеплением.

  6. Сказать внешнему миру «Hello world» через UART.

  7. Переписать «Hello world» с помощью CMSIS, уже с пониманием того, что там происходит.

На этом этапе начальное освоение микроконтроллеров можно считать завершённым. Дальше, видимо, сотни страниц датащитов по записи нужных битов в нужные места, или использование готовых библиотек. В любом случае навыки из списка выше полезны при использовании любых библиотек, т.к. всё растёт из этой базы.

Сразу скажу, что в процессе будет использовано достаточно много инструментов вроде make, ld, gdb, as, gcc и тд, по каждому из них можно книги писать (и пишут). Поэтому, конечно, углубляться в них я не буду, а напротив буду использовать в максимально примитивном виде.

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

★★★

Итак часть 1. Для неё нужен компьютер, собственно плата и программатор ST-Link.

На компьютер нужно установить Arm GNU Toolchain. Его можно скачать тут, возможно в вашем дистрибутиве уже есть готовые пакеты. У вас должна работать команда arm-none-eabi-gdb. Также нужно установить st-link (исходники), он тоже есть во многих репозиториях. Я всё делал на линуксе, но в теории макось и винда тоже должны подойти.

Программатор у меня - фирменный st-link, выломанный из фирменной платы Nucleo. Но больше распространены китайские st-link в виде флешки, они тоже довольно дёшевы.

Программатор нужно подключить к компьютеру и выполнить команду st-info --probe. Он должен отобразить некоторую информацию о программаторе. Если у вас ошибка, попробуйте sudo st-info --probe. Если сработает, значит проблема с правами, копайте udev, ну или работайте от рута.

Плата у меня т.н. blue pill с микроконтроллером STM32F103C8T6. Это дешёвая и распространённая китайская плата. Я не специалист по STM32, но мне показалось, что на базовом уровне они все работают примерно одинаково, поэтому, наверное, информация будет применима и для других плат.

У меня плата после покупки и подключению к компьютеру моргала синим диодом. То бишь в ней уже была какая-то простейшая прошивка для демонстрации работоспособности.

Плату к программатору надо подключить тремя проводами: GND, SWCLK, SWDIO. Я плату и программатор вставлял в два USB-разъёма USB-хаба и у меня всё было хорошо и ничего не сгорело. Также плату можно запитать от программатора, тогда в USB её включать не придётся. Можно ли плату подключить к USB-зарядке, а программатор к компьютеру - я не знаю.

После того, как плата подключена к программатору, st-info --probe должен показать информацию о плате. Если на этом этапе проблемы, нужно их решить.

Вот что выводит у меня:

st-info --probe
Found 1 stlink programmers
  version:    V2J29S18
  serial:     066CFF494849887767255731
  flash:      65536 (pagesize: 1024)
  sram:       20480
  chipid:     0x0410
  descr:      F1xx Medium-density

Теперь попробуем подключиться к плате отладчиком. Это самое интересное. Для этого в одной вкладке терминала запускаем st-util --connect-under-reset

Он выведет что-то вроде

2023-09-08T17:26:46 WARN common.c: NRST is not connected
2023-09-08T17:26:46 INFO common.c: F1xx Medium-density: 20 KiB SRAM, 64 KiB flash in at least 1 KiB pages.
2023-09-08T17:26:46 INFO gdb-server.c: Listening at *:4242...

Что это предупреждение означает, я не знаю, если подключить ногу NRST от микроконтроллера к программатору, оно не исчезает. Но вроде всё работает, поэтому будем его игнорировать.

Собственно видно: что st-util запустил TCP-сервер на порту 4242. К этому серверу будет подключаться gdb.

Во второй вкладке запускаем arm-none-eabi-gdb (возможно обычный gdb тоже подойдёт, я не знаю, честно говоря, в чём отличие):

arm-none-eabi-gdb 
GNU gdb (Arm GNU Toolchain 12.3.Rel1 (Build arm-12.35)) 13.2.90.20230627-git
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=arm-none-eabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://bugs.linaro.org/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) 

И дальше подключаемся к вышеупомянутому серверу:

(gdb) target remote 127.0.0.1:4242
Remote debugging using 127.0.0.1:4242
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0x08000130 in ?? ()
vbr ★★★
() автор топика

Итак мы подключились отладчиком к плате. В последней строчке у вас скорей всего будет другой адрес, не суть.

Теперь пару слов про то, как вообще работает этот микроконтролер. Я, конечно, буду упускать много деталей, попытаюсь передать суть.

Вообще у него всё взаимодействие со всеми устройствами ведётся через оперативную память (и прерывания). Это 32-битный процессор, т.е. он может адресовать 4 ГБ памяти. Конечно самой памяти у него гораздо меньше, на моём устройстве 20 кибибайтов оперативной памяти и 64 кибибайта флеш-памяти. Поэтому адресного пространства хватает и на оперативную память, и на флеш-память и на все остальные периферийные устройства. Если быть более конкретным, то оперативная память начинается с адреса 0x2000 0000, а флеш-память начинается с адреса 0x0800 0000. А также имеется несколько десятков периферийных устройств, каждое из которых имеет свои участки памяти, через которые идёт взаимодействие с этим самым устройством. Всё взаимодействие по сути сводится к чтению и записи данных по адресам памяти.

Итак мы подали питание. У него есть два пина BOOT0 и BOOT1. На плате они настраиваются двумя перемычками. Есть три варианта загрузки, нас интересует загрузка из главной флеш-памяти. Для этого нужно выставить BOOT0 в «0» (т.е. соединить с «землёй»), на моей плате этому соответствует положение перемычки ближе к USB-порту. Посмотреть больше информации про это можно в reference manual 3.4. В зависимости от выбранной конфигурации процессор делает доступным выбранное устройство загрузки по адресу 0x0000 0000. Иными словами когда мы грузимся с флеш-памяти, то адрес 0x0800 0000 становится также доступным по адресу 0x0000 0000 (а также все остальные адреса). Далее процессор читает по адресу 0x0000 0000 4 байта и присваивает это значение регистру sp (stack pointer, вершина стека). Далее процессор читает по адресу 0x0000 0004 4 байта и присваивает это значение регистру pc (program counter), иными словами делает переход по указанному адресу. Ну и, собственно, начинает работу. Читает инструкцию, выполняет и тд.

Помимо этого у процессора есть возможность его отладки. Собственно что мы и сделали, подключив к нему программатор. В этом режиме он после инициализации ничего не начинает выполнять, а ждёт команды от отладчика, а также даёт возможность отладчику читать значения всех регистров, любого адреса оперативной памяти и тд.

Собственно для начала прочитаем по 8 байтов по адресам 0x0000 0000 и 0x0800 0000, убедимся, что они действительно совпадают, а также попробуем понять их смысл.

(gdb) x/2z 0x00000000
0x0:	0x20004ffc	0x08000131
(gdb) x/2z 0x08000000
0x8000000:	0x20004ffc	0x08000131

Команда x читает значения из памяти по указанному адресу. /2z говорит о том, что мы хотим прочитать 2 слова (т.е. 4-байтовых значения) и напечатать их в 16-ричном формате.

Как видно, по обоим адресам действительно лежат одинаковые данные. Иными словами, если верить описанию выше, то после инициализации регистру sp должно было присвоиться значение 0x20004ffc, а регистру pc значение 0x08000131. Сейчас мы этом проверим, а пока попробуем чуть-чуть похулиганить. На моей плате 64 кибибайта флеша, поэтому попробуем прочитать пару слов в самом конце.

(gdb) x/2z 0x08000000 + 64 * 1024 - 4
0x800fffc:	0xffffffff	Cannot access memory at address 0x8010000
(gdb) 

Собственно как и ожидалось - последнее слово ещё что-то содержит, а после него уже адреса недоступны для чтения. Поэтому командой x можно исследовать что угодно без всяких опасений.

Теперь проверим значения регистров:

(gdb) print/z $sp
$1 = 0x20004ffc
(gdb) print/z $pc
$2 = 0x08000130

Для чтения значений используется команда print. Регистры доступны через синтаксис $sp, $pc, $r0 и тд. Также все регистры можно посмотреть командой info registers. Важно понимать разницу между print $sp и x $sp. Первая команда печатает значение регистра, вторая команда значение регистра интерпретирует, как адрес и печатает значение из памяти.

Собственно видно, что со значением регистра $sp всё ожидаемо - он действительно загрузился из адреса 0x0000 0000, а вот $pc оказался на единицу меньше. Тут можно много рассказывать, но это всё не очень интересно - просто запомните, что в архитектуре arm cortex адреса инструкций всегда чётные, при этом значения указателей иногда нечётные. Собственно адрес 0x0000 0004, с которого начнётся выполнение, должен быть нечётным, при этом младший бит обнулится перед переходом. Также инструкции перехода BX, BLX требуют то же самое: младший бит адреса, на который выполняется переход, должен быть равен единице, но при этом переход делается на адрес с нулевым младшим битом. А команды B и BL не требуют, вот такая особенность.

В итоге процессор перешёл на адрес 0x08000130 и остановился. На вашей плате скорей всего все эти значения будут немного другими, я «заводскую» прошивку стёр, но принцип ровно тот же. На всякий случай напомню, что по адресу 0x08000130 у нас доступна флеш-память, т.е. это код из флеш-памяти.

Теперь можно попробовать дизассемблировать инструкцию, которая сейчас будет выполняться:

(gdb) x/i $pc
=> 0x8000130:	add.w	r0, r0, #1

Как видно, для дизассемблирования используется ровно та же команда x (examine). Только вместо /z используется формат /i. z это шестнадцатеричный формат, i это интерпретация числа, как инструкции. Для интереса покажу ещё несколько форматов, думаю, всё и так очевидно.

(gdb) x/z $pc
0x8000130:	0x0001f100
(gdb) x/d $pc
0x8000130:	127232
(gdb) x/x $pc
0x8000130:	0x0001f100
(gdb) x/t $pc
0x8000130:	00000000000000011111000100000000

Форматы /z и /x означают одно и то же, но в некоторых случаях /x не добавляет нули слева, а /z всегда добавляет нули.

Иными словами 4 байта 01 00 f1 00 процессором интерпретируются, как команда add.w r0, r0, #1. Эта команда аналогична псевдокоду r0 := r0 + 1.

Важно понимать, что эта команда ещё не выполнена, процессор только готовится её выполнить.

Теперь распечатаем значение регистра r0, далее выполним команду и распечатаем значение регистра ещё раз.

(gdb) p/z $r0
$6 = 0x48b503b6
(gdb) stepi
0x08000134 in ?? ()
(gdb) p/z $r0
$7 = 0x48b503b7

Как видно, в регистре r0 было какое-то непонятное значение и процессор действительно увеличил это значение в регистре r0 на единицу. После команды stepi процессор перешёл на 4 байта вперёд и готовится выполнить следующую команду. Посмотрим, что там за команда.

x/i $pc
=> 0x8000134:	b.w	0x8000130

Это команда branch, команда безусловного перехода по указанному адресу. Если её выполнить, то процессор вернётся на предыдущую инструкцию. Ну собственно так и будет туда-сюда прыгать, считая от 0 до 4 миллиардов по кругу, пока не выключим питание.

В псевдокоде эта программа выглядит примерно так:

loop: r0 := r0 + 1;
goto loop;

В дальнейшем я буду иллюстрировать программы на ассемблере псевдокодом, похожим на C, по крайней мере мне так проще.

На вашей плате из магазина программа будет другая. Но принципы работы те же.

На этом первый пункт закончен. Мы запустили плату, подключились к ней и убедились, что там внутри действительно процессор, а не просто лампочка.

vbr ★★★
() автор топика
Последнее исправление: vbr (всего исправлений: 1)

Ага, говорили они:

  • дайте нам раздел со статьями
  • вот вам раздел со статьями
  • но нет, мы продолжим постить длиннотексты в толксы
vvn_black ★★★★★
()
Последнее исправление: vvn_black (всего исправлений: 1)
Ответ на: комментарий от token_polyak

USB это моя цель, но я не уверен, что осилю без библиотек, всё же USB это сложно. Что получится - напишу.

vbr ★★★
() автор топика
Ответ на: комментарий от vbr

Статьи у меня пока нет

А вот два длиннопоста это что такое? Это как раз статья для соответствующего раздела, я так думаю.

vvn_black ★★★★★
()
Ответ на: комментарий от vbr

Мне кажется логичным оформить всё это в 7 отдельных постов в разделе «Статьи».

vvn_black ★★★★★
()

Годно, пиши еще.

otto ★★★
()

Написано лучше, чем я ожидал, когда открывал тему.

Буду ждать следующие части, спасибо.

si0 ★★★
()

Написать простейшую программу на ассемблере

Бесполезная трата времени. Выкинуть и перейти сразу на си.

ox55ff ★★★★★
()

Пункты 2 3 нужны лишь тем, кто совсем уж далек от принципов работы мк. Или тем, кто собирается копать вглубь конкретно stm32.

vaddd ★☆
()
Ответ на: комментарий от vbr

Вообще у него всё взаимодействие со всеми устройствами ведётся через оперативную память

Всё взаимодействие по сути сводится к чтению и записи данных по адресам памяти.

Это всё же некорректно. Взаимодействие ведётся не через память, а через регистры этих самых устройств, доступных через общее адресное пространство. Регистры не являются памятью. Порты ввода/вывода, если угодно.

apt_install_lrzsz ★★★
()

Берешь куб, конфигуришь железо. Profit. Все что касается ассемблера и регистров отправляешь в корзину.

Vit ★★★★★
()

Опубликовал в черновике Осваиваем STM32 снизу: часть 1 - подключаем и исследуем плату

Не очень понятно - доступен ли он пользователям или только мне? Публиковать ли его до завершения написания, редактируя в процессе? Или только когда всё будет готово? Как правильно?

В принципе всё выложил в гитхабе, если кому удобней там.

vbr ★★★
() автор топика
Последнее исправление: vbr (всего исправлений: 1)
Ответ на: комментарий от vaddd

Пункты 2 3 нужны лишь тем, кто совсем уж далек от принципов работы мк

Скажем так, я пишу то, чего мне не хватало, собранного в одном месте, когда я разбирался. Поэтому - да, целевая аудитория - те, кто с одной стороны немного разбирается в программировании, с другой стороны - только что принесли плату с магазина радиотоваров и не знают, что с ней дальше делать. STM32 Cube лично меня «подавил» своей монструозностью. Для профессионалов он, наверное, хорош. Но я предпочитаю сначала разобраться с инструментарием на нижнем уровне, а потом уже сверху добавлять знания. Сейчас я тот же выхлоп куба уже способен читать чуть более осознанно. Может для обычных компьютеров начинать с ассемблера и не нужно, а для микроконтроллера с 20 КиБ памяти - я считаю, что нужно, хотя бы самую малость потрогать и не пугаться листинга дизассемблера.

vbr ★★★
() автор топика
Ответ на: комментарий от vbr

Я ковыряюсь с USB на STM32, там благодаря Cube Mx не так сложно всё, а сложно конкретно то что поверх, например USB HID, мне тяжело было привыкнуть к идеологии USB HID

I-Love-Microsoft ★★★★★
()

Блин, автор, респект тебе! Я как раз начал в stm32 вкатываться, железки пришли. А тут твой ликбез – как найденный!

Alden ★★★
()
Ответ на: комментарий от vbr

Скажем так, я пишу то, чего мне не хватало, собранного в одном месте, когда я разбирался. Поэтому - да, целевая аудитория - те, кто с одной стороны немного разбирается в программировании, с другой стороны - только что принесли плату с магазина радиотоваров и не знают, что с ней дальше делать. STM32 Cube лично меня «подавил» своей монструозностью. Для профессионалов он, наверное, хорош. Но я предпочитаю сначала разобраться с инструментарием на нижнем уровне, а потом уже сверху добавлять знания. Сейчас я тот же выхлоп куба уже способен читать чуть более осознанно. Может для обычных компьютеров начинать с ассемблера и не нужно, а для микроконтроллера с 20 КиБ памяти - я считаю, что нужно, хотя бы самую малость потрогать и не пугаться листинга дизассемблера.

Такая же фигня. Я не понимаю электронщиков, которые ставят себе паленую Cube и фигачат в ней говнокод, перетыкая мышкой режимы работы ножек и включая прерывания галочкой в свойствах проекта. Это так странно.

За статьи отдельный респект.

Xintrea ★★★★★
()
Последнее исправление: Xintrea (всего исправлений: 1)
Ответ на: комментарий от vbr

У меня статьи короче чем ваш один пост. Оформляйте в статьи и не парьтесь.

vbcnthfkmnth123 ★★★★
()
Ответ на: комментарий от vbr

Наверное палёный кейл?

Да, ошибся.

Куб вроде бесплатный

Но проприетарный. И поэтому последние версии не хотят работать с китайскими клонами STM.

Xintrea ★★★★★
()
Закрыто добавление комментариев для недавно зарегистрированных пользователей (со score < 50)