LINUX.ORG.RU

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

 ,


18

2

Часть 1 Часть 2 Часть 3 Часть 4 Часть 5 Часть 6 Часть 7 Часть 8 Часть 9

Все файлы можно взять тут.

В данной серии статей мы попробуем поработать с процессором STM32 с помощью GNU утилит, немного познакомимся с ассемблером и отладкой.

Примеры написаны для популярной платы blue pill, построенной на микроконтроллере STM32F103C8T6.

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

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

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

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

  4. Написать простейшую программу на C, которая в цикле прибавляет значение глобальной переменной. Скомпилировать из неё прошивку, залить на плату и понаблюдать её работу с помощью с помощью gdb.

  5. Помигать диодом на С (дальше всё на С).

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

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

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

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

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

  • STM32F103x8 Datasheet. Это спецификации микроконтроллера, тут можно посмотреть варианты корпусов, выходы, электрические характеристики.
  • STM32F103xx Reference Manual. Тут вся справочная информация по всем битам и байтам данного микроконтроллера.
  • STM32F10xxx Programming Manual. Тут справочная информация по ARM: все инструкции, описание работы процессора.
  • GNU debugger GDB. Это отладчик.
  • GNU make. Это утилита для сборки проектов.
  • GNU assembler as. Это ассемблер.
  • GNU linker ld. Это компоновщик (линкер).

Часть 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 работают примерно одинаково, поэтому, надеюсь, информация будет частично применима и для других плат. Некоторые источники утверждают, что на дешёвых blue pill микроконтроллеры ненастоящие, не от STM, а от китайских клонов. Насколько я могу судить, мой экземпляр соответствует документации от STM, поэтому для целей обучения вполне годится. Если вас интересует оригинальный микроконтроллер, присмотритесь к серии плат Nucleo, но они гораздо дороже.

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

Плату к программатору надо подключить тремя проводами: GND, SWCLK, SWDIO. Также плате нужно питание, лучше всего подать питание четвёртым проводом от программатора. На плате эти пины расположены с противоположной 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 ?? ()

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

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

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

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

Итак мы подали питание. У микроконтроллера есть два пина 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 (от слова eXamine) читает значения из памяти по указанному адресу. Суффикс /2z говорит о том, что мы хотим прочитать 2 слова (т.е. 4-байтовых значения) и напечатать их в 16-ричном формате.

Как видно, по обоим адресам действительно лежат одинаковые данные. Иными словами, если верить описанию выше, то после инициализации регистру sp должно было присвоиться значение 0x2000_4ffc, а регистру pc значение 0x0800_0131. Сейчас мы этом проверим, а пока попробуем чуть-чуть похулиганить. На моей плате 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 не требуют, вот такая особенность.

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

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

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

Как видно, для дизассемблирования используется та же команда x. Только вместо /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 миллиардов по кругу, пока не выключим питание или не нажмём кнопку reset.

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

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

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

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

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

★★★★

Проверено: hobbit ()
Последнее исправление: hobbit (всего исправлений: 8)

Вот это ты мощно загнул, конечно, респект и уважуха.

на текущий момент статья находится в процессе написания, опубликованы 5/8 частей

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

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

Первый вариант проще для тебя. Второй, как мне кажется, легче для чтения (большие портянки мотать тяжелее, да и разбитый по главам текст воспринимать легче). Кроме того, неподтверждённые материалы регистрантам, конечно, видны, но далеко не всякий регистрант будет регулярно в разделе «Статьи» кликать по ссылке «Неподтверждённые».

Подумай сам, как лучше…

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

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

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

Думаю, эта часть готова, потихоньку буду следующие добавлять, возможно не все сегодня, пока 6/8 готово, до конца уже точно допишу.

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

Начинаем потихоньку подтверждать :)

hobbit ★★★★★
()

Так-то, если по-быстрому, можно platformio поставить и в кубе периферию законфигурять. Если кодишь эпизодически, а не вкалываешь 24х7 - нет смысла в ассемблер с регистрами забуриваться и манулы на пиццот страниц в голове держать.

Если рассматривать как тьюториал не по stm32 а по микроконтроллерам вообще - наверное годно.

Vit ★★★★★
()

Афтар, ты крутой и жжош аццки!

Gonzo ★★★★★
()

Статья хорошая, хотя я бы пропустил ассемблер, и начал сразу с си.

Маленькое замечание: передавать параметр --connect-under-reset при отсутствии подключения NRST нет смысла. Об этом и говорит предупреждение. (Почему предупреждение не пропадает при подключении NRST - не знаю).

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

Если этот параметр не передавать в программу st-util, то gdb после подключения видит программу в текущем виде. Если передавать, то в только что запущенном. Поэтому reset как-то работает. Полагаю, что swd позволяет это. А «полноценный» reset мог бы быть полезен, когда процессор вообще не отвечает на запросы. Например я его в такое состояние вводил выполнением wfi, а выводить (дебаггером) так и не научился, хотя пробовал всякое. Грешу на китайские корни, сегодня купил оригинальный nucleo, буду его мучать.

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

А, понятно. Значит в st-util этот ключ означает ещё и сброс контроллера при старте. А connect under reset (в обычном понимании, у программатора от ST и у openocd) - это подключение к контроллеру с прижатым резетом. Чтобы программа не успела начать выполняться и сделать что-то, что помешает отладке (войти в спячку или отключить ножки программирования).

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

В общем на Nucleo, где, видимо, встроенный программатор подключен по фен-шую, этого предупреждения нет и reset он умеет делать даже когда процессор сидит в wfi, удобно, не надо ерундой страдать.

vbr ★★★★
() автор топика

Отличные статьи на близкую мне тему. Автор молодец!

wiseguy
()
Ответ на: комментарий от hobbit

@hobbit

Если есть возможность, просьба убрать «на текущий момент серия статей находится в процессе написания» в начале, добавить ссылки на оставшиеся части

[Часть 1](https://www.linux.org.ru/articles/development/17345778)
[Часть 2](https://www.linux.org.ru/articles/development/17350388)
[Часть 3](https://www.linux.org.ru/articles/development/17350412)
[Часть 4](https://www.linux.org.ru/articles/development/17350476)
[Часть 5](https://www.linux.org.ru/articles/development/17350481)
[Часть 6](https://www.linux.org.ru/articles/development/17350556)
[Часть 7](https://www.linux.org.ru/articles/development/17353238)
[Часть 8](https://www.linux.org.ru/articles/development/17353247)
[Часть 9](https://www.linux.org.ru/articles/development/17366122)

и обновить первые параграфы версией с гитхаба (тут).

Также просьба скопировать с гитхаба последнюю версию для второй части (тут), остальные я могу редактировать.

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

Извиняюсь, у меня сейчас интернет очень неустойчивый. Заняться смогу завтра или послезавтра…

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

Ссылки унифицировал, предупреждения убрал. Обновления 1 и 2 частей сделаю, когда под рукой будет комп с линуксом и diff-ом (постараюсь сегодня вечером).

hobbit ★★★★★
()

возможно обычный gdb тоже подойдёт, я не знаю, честно говоря, в чём отличие

Ни в чём. gdb давно (?) идёт multiarch - можно спокойно пользовать Пол года уже так сижу

Поэтому его не включают, например, в сборки федоры

Короче не тратьте время, не собирайте его самостоятельно, просто используйте vanilla gdb )

enik
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.