С чего начать изучение STM32? Какие преимущества я получу от перехода? Настолько ли это легко?
Эти вопросы возникают, пожалуй, у всех, кто когда-либо задумывался об освоении этой платформы.
Я попытался написать такую статью, с ответами на все потенциальные вопросы, какой мне не хватило в начале моего пути.

Введение
Все разговоры о высоком входном пороге придуманы теми, кто не смог его преодолеть. Да, это сложно, но ведь любую задачу можно декомпозировать.
Поэтому, шаг за шагом, разложим по полочкам все тезисы.
Ключевые отличия
В этом блоке будем рассматривать только классические, примитивные платы Arduino, построенные на микроконтроллерах AVR. Например ATMega328, 2560.
Философия
Arduino как платформа создавалась именно для упрощения работы с МК. Это симбиоз готовых отладочных плат, стандартизации плат расширений, а также программных инструментов. Таким образом, любой, не погружённый в тематику человек, мог создать готовое устройство “from scratch”.
Но, на мой взгляд, роковая ошибка случилась тогда, когда вместо игрушечных проектиков начали реализовывать тяжёлый функционал, вследствие чего вся система могла работать не так, как хотелось.
Поэтому, в обществе начало формироваться негативное отношение к Arduino, транслируемое и на остальной embedded.
А трушные эмбеддеры начали пренебрежительно-оскорбительно называть Arduino-программистов “ардуинщиками”, сродни “питонистам”.
Причиной этому обернулась и та самая простота разработки. Программисту сделали лишний слой абстракции и отобрали возможность в него заглянуть. Из-за этого сильно упало качество кода, у людей перестал развиваться скилл оптимизации.
У кого-то опускаются руки, кто-то ищет ответы с помощью других плат семейства. Но, настоящий выход — переход на нормальную платформу — STM32.
Сам микроконтроллер и его окружение тоже создавались с упрощенным взаимодействием. Но, в данном случае, хоть иногда приходится включать голову, что способно привести к более высоким достижениям.
Архитектура
Общее начало двух этих семейств — система команд RISC.
В Arduino исторически использовалась архитектура AVR. (Alf and Vegard’s RISC)
Эта архитектура древняя, разработанная в конце 90-х.
STM32 построены на архитектуре ARM. (Advanced RISC Machine)
Ядра Cortex-M. Первые МК появились в 2007.
Можно было бы сделать поправку на время появления, но так как AVR активно используется и в современности, сравнение актуально.
Главное различие же в том, что AVR — архитектура 8-битная, а ARM — 32-битная.
Что есть разрядность?
Вспомним информатику 9-го класса школы.
8-битное число имеет всего 28 = 256 вариаций.
А 32-битное число: 232 = 4’294’967’296.
Этим исчисляется количество элементарных ячеек (байт) памяти, которые может адресовать (увидеть) контроллер ОЗУ. Поэтому, например, в младших PIC16 размер RAM не более 256 байт.
Безусловно, в таких контроллерах ограничения обходятся мультиплексированием или расширением шины, но не более 16 бит, что есть всего лишь 64 КБайт.
В STM32 пространство памяти общее, с максимальным размером в 4 ГБайт. Поэтому можно подключить внешнюю SRAM и хранить, например, буферы дисплеев. Подробнее про память описано у других авторов.
Гипотетически архитектура ARM поддерживает ширину внутренней шины до 1024 бит.
Но, разрядность определяет не только адресное пространство, ширину командного слова.
Если очень упрощать, то 32-битному процессору требуется кратно меньше инструкций на обработку большого числа, чем 8-битному. Это позволяет достичь более высокой точности в вычислениях при равных временных затратах.
Тактирование
Процессор работает последовательно, каждый раз вызывая очередную инструкцию из памяти. Их выполнение занимает определённый промежуток времени, который измеряется в тактах. Пример для Cortex-M4.
Временной интервал одного такого промежутка задаёт система тактирования. Иными словами, тактовая частота процессора — количество тактов в секунду.
В ранних AVR эта система очень примитивная, поддерживает максимальную частоту 16…20 МГц.
В STM32 используется более сложная схема. В качестве базового источника используется внешний или внутренний осциллятор. Он может задавать частоту как напрямую, так и передать её на PLL (ФАПЧ), который обеспечивает разгон вплоть до двух сотен мегагерц.
Аппаратные блоки и периферия
И так высокую скорость работы можно ещё увеличить, для этого в Cortex используются вспомогательные аппаратные блоки.
Например, блок FPU (Floating Point Unit) для работы с дробными числами.
В некоторых сериях есть расширение инструкций DSP (Digital Signal Processing) для быстрой цифровой обработки (аналоговых) сигналов.
Везде есть контроллер DMA (Direct Memory Access) для обмена данных периферии и памяти без участия процессора.
В старших МК используют различные ускорители, кодеки, а также кэш-память L1.
Для проверки данных везде есть блок CRC. Для защиты данных бывает MPU (Memory Protection Unit), а также блоки криптозащиты, реализующие алгоритмы AES, MD5 и др.
Микроконтроллеры STM32 богаты периферией. Помимо стандартных SPI, I2C, UART, в некоторых МК присутствуют: USB, SDIO, Quad-SPI, I2S, SPDIF а также специфические: FSMC, CAN(-FD), HDMI-CEC, и так другие…
Устанавливаются: 12-битный АЦП, 12-битный ЦАП, встроенные аппаратные компараторы и операционные усилители.
Большое количество таймеров, как 16-битных, так и 32-битных. В некоторых сериях есть высокоточный таймер для применения, например, в высокочастотных DC-DC преобразователях.
В большинстве МК есть RTC (Real Time Clock), содержащий часы, календарь и будильник.
Отладка
Далеко не во всех AVR есть блок отладки. А там, где есть, он очень примитивный. А в Arduino IDE ещё и бесполезный: такой функционал там не предусмотрен.
В отличие от AVR, отладка есть во всех STM32. Он позволяет: отслеживать выполнение команд в реальном времени, добавлять точки останова, просматривать все переменные и регистры. Происходит это с помощью интерфейсов JTAG или SWD.
Корпуса
Выбор корпусов очень широк.
- Классические, с ножками: LQFP, TSSOP
- Без ножек: QFN, LGA
- На шарах: BGA, WLCSP
Количество лап варьируется от 8 до 240.
Как и в AVR, используется топология “Порт-пин”.
Например: Port A Pin 8 — PA8.
На пин можно назначить как GPIO (с подтяжками вверх и вниз), так и альтернативную функцию (периферию). Некоторые ноги мультиплексируются. К примеру TIM2_CH2 можно назначить как на PA1, так и на PB2.
Есть интересная особенность, касаемая корпусов. Для разных серий МК, назначение ног может быть частично или полностью совместимо. (AN3364)
Железо
Остались ли теперь сомнения для перехода? После того, как убедились в массе преимуществ STM32 над Arduino, надо понять, из чего выбирать.

Обзор семейств
Вся линейка микроконтроллеров использует Cortex-M.
Её делят различные семейства, которые ориентированы на использование в конкретных системах. Например, F1 предназначен для управления двигателями, а G4 — DC-DC преобразователями.
Рассмотрим отличия в таблице. Столбцы выстроены по ядрам, а строки по кейсу использования.
High Performance | F2 120 MHz M3 | F4 180 MHz M4F | H7 550 MHz M7F + 240 MHz M4F | |
F7 216 MHz M7 | ||||
Mainstream | G0 64 MHz M0+ | F1 72 MHz M3 | G4 170 MHz M4F | — G4 и F3 оптимизированы для смешанных сигналов (цифровые + аналоговые) |
F0 48 MHz M0 | F3 72 MHz M4F | |||
Low-power | L0 32 MHz M0+ | L1 32 MHz M3 | L4+ 120 MHz M4F | U5 160 MHz M33 |
L4 80 MHz M4F | L5 110 MHz M33 | |||
Wireless | WL (LoRa) 48 MHz M4F (Main) 48 MHz M0+ (Radio) | WB (BT) 64 MHz M4F (Main) 32 MHz M0+ (Radio) |
Сводка особенностей Cortex-M:
- Cortex-M0 — ARMv6-M
Самый простой и дешёвый, поддержка только базовых инструкций. (Часть Thumb-1,2)
Трёхуровневый конвейер. - Cortex-M0+ — ARMv6-M
Оптимизированное ядро M0.
Двухуровневый конвейер для уменьшения энергопотребления. - Cortex-M3 — ARMv7-M
Расширенный набор инструкций. (Thumb-1,2, Divide)
Трёхуровневый конвейер с прогнозированием ветвлений. - Cortex-M4 — ARMv7E-M
Расширение ядра M3: добавление инструкций DSP.
В модификации M4F добавлен блок FPU (вычислений с плавающей запятой) одинарной точности. - Cortex-M33 — ARMv8-M
Концептуально похож на M4. Отличие в наличии инструкций TrustZone — защищённой зоны исполнения. - Cortex-M7 — ARMv7E-M
Шестиуровневый суперскалярный конвейер с предсказанием ветвлений. В 2 раза энергоэффективнее, чем M4.
64-битная шина данных.
FPU двойной точности.
Опциональный кэш.
МК на базе M0(+) призваны заменить 8-битные МК.
Ядро M3 предназначено для простых цифровых применений: работа с цифровыми шинами, генерация ШИМ-сигнала, и т.д.
M4 необходимо использовать в тех устройствах, где раскроется потенциал DSP: анализ/синтез цифровых и аналоговых сигналов, фильтрация, преобразование напряжения.
М7 рационально только в сверхсложных системах с огромным количеством вычислений.
Нейминг
Поначалу крайне легко запутаться в многообразии всех МК. Научимся читать по примеру:
STM32F401RET6TR
- STM32 — общее для всех МК (есть ещё STM8)
- F401 — Семейство F4, серия 401
- R — префикс корпуса: кол-во пинов. Тут 64 ноги
- E — объём памяти, здесь 512 КБ
- T — постфикс корпуса: исполнение. LQFP-64 в данном случае
- 6 — Temperature Grade: температурный диапазон
- TR — опционально, упаковка Tape&Reel (бобинная лента). Если отсутствует, то Tray (лоток россыпью)
Мы рассмотрели закупочную запись (order code). Обычно это описываются в конце даташита. Во всей документации и справочниках такой МК будет прописан кратко: STM32F401RE. То есть без указания конкретного корпуса, так как кристалл одинаковый.

Поиск плат
Во избежание проблем на первых этапах, новичку стоит обратить внимание на фирменные отладочные платы от ST. Они представлены в двух вариантах: Discovery и Nucleo.
Платы Nucleo содержат необходимый минимум для запуска микроконтроллера: обвязку, систему питания с защитами, отладчик. Все ножки МК выведены на гребёнки, соответствующие единому стандарту распиновки.
Платы бывают разного размера:
— 32 ножки: формат Arduino Nano.
— 64 ножки: чуть больше, чем Arduino Uno, поддержка всех её шильдов.
— 144 ножки.

Discovery же, вдобавок перечисленному выше, обладают периферией для ознакомления. Это могут быть инерциальные сенсоры, микрофоны, датчики окружающей среды, референс-дизайны (например, для DC/DC), дисплеи.

Другие производители тоже изготавливают свои платы. Например, серия Waveshare Core.

Все остальные платы, не имеющие бренда, изготавливаются всеми подряд. Их качество, как и оригинальность используемых чипов, иногда ставится под сомнение, поэтому приобретать и использовать их можно только на свой страх и риск.
Одна из популярных плат называется BluePill, на которой установлен МК STM32F103C8T6.
Но, в текущих условиях дефицитов, при своей цене, она потеряла актуальность и рациональность. Слишком бедная периферия, сравнительно небольшой объём памяти, ошибки в схемотехнике, шанс нарваться на подделку.

Новый тренд на рынке — плата BlackPill. Сравнение с BluePill. В ней используется F401CCU или F411CEU. За небольшую доплату получаем кратный прирост памяти и тактовой частоты, расширенный набор периферии и таймеров, а также порт USB-C с функцией OTG. То есть способный выполнять роль как Device, так и Host, что может помочь в реализации множества идей.
Оригинальный репозиторий.

Программаторы
Способов прошить микроконтроллер существует великое множество.
Все МК имеют аппаратный bootloader, управляемый физическими пинами BOOT. Он позволяет загружать прошивку с различных периферийных шин. На всех МК для этого по умолчанию доступен UART. Поэтому, в качестве программатора может выступать любой USB-RS232 преобразователь.
Другой вариант — программный bootloader. Это небольшой кусок программы, который по внешним признакам может подменять основную прошивку. Напрмер, для обёртки STM32Duino используют DFU, который обеспечивает загрузку через USB.
Это всё про загрузку. А как же обещанная полноценная пошаговая отладка?
Для этого уже необходимо специальное дополнительное устройство, именуемое дебаггером. Именно он и является мостиком между железом и IDE.
Дебаггер/отладчик принимает команды GDB и преобразует их в команды шины отладки: JTAG или SWD.

ST-Link — фирменный отладчик от производителя. Его существует 3 версии:
v1 — устаревшая. На базе F103C8
v2 — устаревшая. На базе F103CB/RB
v2.1 — полу-устаревшая. Программная доработка v2. Возможности: режим SWD, режим SWIM (для STM8), канал SWO, адаптер UART (VCP), возможность загрузки прошивки в режиме “флешки” (Mass Storage Device).
v3 — актуальная. На базе F723. Все функции v2.1, повышенная частота SWD, режим моста для SPI/UART/I2C/CAN/GPIO.

ST-Link установлен на платах Nucleo и Discovery. Посмотреть версию можно в Product Selector: Nucleo, Discovery. v2 можно отломить от Nucleo и получить отдельную плату отладчика.

Схема этого программатора из Nucleo была размещена в каком-то из даташитов, а прошивку давно сумели скопировать.
Поэтому, можно изготовить собственный ST-Link v2.1. Есть готовое устройство у Голинского Константина.

Или можно приобрести примитивный свисток у китайцев. Я начинал именно с такого. Статья про переделку.

Эту же прошивку можно использовать для превращения BluePill в полноценный отладчик.

Существует множество других отладчиков. Индустриальным стандартом считается Segger J-Link. Он имеет свои преимущества, но все они уничтожаются неподъёмной для хоббиста ценой. (более 300-500 евро)

Но, на самом деле, отладочные интерфейсы у ARM хорошо задокументированы. Поэтому, при желании, можно реализовать свой Host-отладочный интерфейс, разработать, например, что-то сугубо проприетарно-корпоративное.
По этому принципу был создан open-source отладчик BlackMagic Probe. Платы уже на руках, а статья про него выйдет в начале-середине февраля.

Софт и прошивка
Вся логика работы микроконтроллера описывается в прошивке. Она представляет из себя набор последовательно выполняемых команд. Чаще всего для этого используют языки Си или С++. Компиляция происходит через специализированный GNU Embedded Toolchain. Отладка через стандартный GDB.
Всё как для настольных платформ, только инструменты свои.
Взаимодействие с периферией осуществляется с помощью библиотеки CMSIS. Это есть низшая прослойка между железом и программным кодом. Сама по себе она предоставляет удобный доступ ко всем процессорным и периферийным регистрам.
Родные инструменты
Освоить CMSIS с ходу довольно сложно. Поэтому производители (вендоры) изобретают некоторые “обёртки” вокруг этого низкого уровня. Конкретно у ST это LL (Low-Layer) и HAL (High Abstraction Level). Они не только упрощают работу, но и обеспечивают переносимость кода между разными МК.
Ранее был SPL, но более он не поддерживается.

Все эти библиотеки объединяет программный пакет STM32Cube. Он включает в себя API и периферийные драйверы для микроконтроллеров, а также инструменты разработки и отладки.
Одна из таких утилит — CubeMX. Это кроссплатформенный графический редактор конфигурации микроконтроллера и генератор начального кода.

Нажмите для увеличения
CubeMX включен в состав CubeIDE, бесплатной среды разработки на базе Eclipse.
Это полностью независимый инструмент для создания и отладки прошивок на все доступные МК STM32.

Нажмите для увеличения
У ST для ST-Link есть свой GDB-сервер. Его работа описана в другой статье.

Сторонние инструменты
Самое глупое, что можно использовать — фреймворк STM32Duino. Это тупиковая веточка, такое не уважаем.

Следующий по сложности — ARM Mbed. Это разработка ARM преимущественно для IoT-устройств. Ориентирована, в основном, на Nucleo.
Те, кому не нравится Eclipse (CubeIDE), используют другие IDE.
Есть специфические для embedded: Keil от ARM, IAR, MikroC, и т.д.
Для Visual Studio есть плагин: Visual GDB.
Для VS Code существуют такие плагины: плагин1, плагин2. [Инструкция]
При этом используют сторонний GDB-сервер: OpenOCD.

Я использую платную IDE CLion, в которой присутствуют инструменты для embedded, в связке с родным GDB-сервером. Подробнее про это можно почитать тут.

Использование библиотек
В языке Си под библиотеками обычно подразумеваются скомпилированные статические/динамические .a/.so файлы.
Но терминология, пришедшая из Arduino, предлагает называть так обычную пару .h/.c файлов. Так как вся работа в основном с периферией, то я обычно называю это драйверами.
В любом случае, что библиотека, что драйвер, содержат заголовочный .h файл, который нужно скормить линкеру. Т.е. недостаточно просто сделать #include, как в Arduino.
В Arduino была культура создания готовых драйверов стиля “подключил и пошёл”.
С STM32 всё не так. Можно брать как ардуиновские наработки и переписывать всё взаимодействие с периферией под платформу, так и искать чужой код на GitHub или в примерах/уроках, переделывать их под свой стиль.
Либо же писать свои, с нуля, по даташитам.
Правила хорошего тона — создавать отдельный слой абстракции, платформонезависимые методы обращения к шинам, для их вызова в других методах. Таким образом, при смене платформы/обёртки, потребуется поменять всего несколько строк кода.
Например:
static inline void USER_SPI_Read(uint8_t *data, uint16_t len, uint8_t register){
// Можно написать под HAL
HAL_SPI_Read(data, len, register, timeout);
// Или под LL
LL_SPI_Read(...);
// Или под CMSIS
.....
}
void SensorCollect(uint16_t *out_data){
uint8_t buf;
USER_SPI_Read(out_data, 1, 0xAFU);
*out_data = .....;
}
Если используется HAL, то достаточно закинуть .h-файлы в Inc, а .c-файлы в Src. Но, этот вариант не только слишком простой, но и засоряющий визуальную структуру файлов приложения.
Для всех внешних ресурсов удобнее создать отдельную папку. Например, Libs.
В Eclipse подключить директории можно аж тремя способами.
1. “Add/remove include path..” в контекстном меню — самый удобный.

2. “Paths & Symbols” в Properties проекта.
Обратите внимание на написание директории, по умолчанию туда добавляются лишние верхние уровни.

3. В “Settings” билдера.

Результат должен быть таким:

В CLion используется система CMake, поэтому директории хэдеров там прописываются вручную. Ничего сложного в этом нет, IDE всё сама подскажет. Вносятся они в файл CMakeLists.txt, а после теста — в _template, чтобы изменения после регенерации не затёрлись.

Поиск информации
Первичный источник знаний — официальные документы. Даташиты, reference и programming мануалы, application notes. Есть удобная подборка.
Помимо этого, есть множество статей и уроков. Вот моя подборка:
- istarik.ru — хорошо разжёванная теория, а также комьюнити в telegram.
- narodstream.ru — очень много уроков по периферии. Не всегда они супер-качества, но помогают понять многое.
- mypractic.ru — иногда про более низкий уровень взаимодействия.
- avislab.com — старые, но полезные статьи.
- we.easyelectronics.ru — углублённый уровень.
- DiMoon.ru — уроки по CMSIS.
- Bare Metal STM32 — тоже CMSIS.
- stm32asm.ru — для
извралюбителей хардкора. - Топик на форуме AlexGyver
Каналы на YouTube:
- Олег Волков — конкретный цикл роликов про переход с Arduno.
- Владимир Мединцев — крайне познавательные и полезные на практике ролики.
- TDM Lab
- NR.electronics
- Home Made
Заметки
Для embedded-систем есть целый стандарт написания кода. Называется он MISRA. Это свод правил для обеспечения читаемости и безопасности кода.
Проверка на MISRA работает, например, в IAR и CLion. Также можно использовать статические анализаторы. PVS-Studio, например.
Одно из этих правил касается запрета динамического выделения памяти. Например, Arduino одобряет использование таких структур для представления строк. Это хоть и удобно, но всегда убивает скорость и безопасность. Поэтому, для строк лучше использовать классический массив char[].
То же касается и использования C++. Его использование оправдано только в сверх-сложных системах для создания одинаковых объектов с разными свойствами. Например, опроса десятка датчиков.
Но, в любом случае можно обойтись структурным подходом. А так как поначалу будет использоваться HAL, написанный на структурах, то не стоит создавать кашу из абстракций.
Также MISRA обязывает использовать типы данных определённого размера, согласно stdint.h.
Вместо int — int16_t. Вместо unsigned int — uint16_t.
Есть лайфхак, взятый из kernel. Для того, чтобы меньше печатать, следует использовать алиасы для этих типов.
Например:
#include <stdint.h>
typedef uint8_t u8_t; // формат: typedef имя псевдоним
typedef int8_t i8_t;
Ещё один лайфхак касается HAL. Дело в том, что очень тяжело работать с громоздкими файлами, которые генерирует CubeMX. Куча визуального мусора, есть риск промахнуться мимо USER CODE и потерять строки при регенерации.
Поэтому, я создаю отдельную пару файлов app_main и работаю с ней. В ней размещается новый главный метод main_app, который и вызывается из сгенерированного кубиком кода.


Что касается CubeMX — кому-то не открою большой тайны, но сам я нашёл этот функционал спустя год практики. Как оказалось, используя Ctrl, можно переносить ножки в альтернативные положения. Это серьёзно упрощает задачу по комбинаторике всех ног.

Вывод
Это был краткий ввод в STM32. Мы разобрались со всеми базовыми понятиями, теперь можно приступать к самостоятельному изучению и определению собственного пути.
Этот путь довольно тернист, но в конечном итоге уровень всех навыков повысится.
Можно не ограничиваться STM32, а после прыгнуть на другие ARM-микроконтроллеры, работа со всеми ними на самом деле очень похожа.
Начать можно с переноса своих или чужих Arduino-проектов на STM. Или же создать какое-то устройство, полезное в быту. Так изучение будет наиболее оправданным и мотивированным.
Если задача изучения нацелена не только на хобби, но и на трудовую деятельность, то вакансии можно начать искать уже примерно через год практики. В основном все требуют хорошего знания HAL, LL и опыта работы с цифровыми шинами. Также важно иметь проекты в портфолио.
Самое главное — верить в себя, не сдаваться и не опускать руки. Всё получится! Стоит только захотеть.
Если статья была полезной, помогла что-то понять, есть специальный раздел для оказания благодарности.
Комментарии всегда открыты для вопросов и дискуссий.
Ссылки
Реферальные ссылки на покупку железок на AliExpress. Цена остаётся неизменной, а блог получит небольшой бонус.
- BluePill, BlackPill и ST-Link свисток
- BlackPill оригинал
- Отладка на F7 для OpenMV
- Nucleo F411
- Отладка на F407 — Большая плата с флешкой памяти и опциональным дисплеем
- ST-Link v3
- ST-Link v2 в нормальном корпусе
- Логический анализатор 8 каналов 24 МГц
- Логический анализатор 16 каналов 100 МГц