Знакомимся с микроконтроллерами на ядрах ARM Cortex-M

Давно я не проявлял никакой активности в блоге, но Kaimi что-то разошёлся со статьями по пентесту в последнее время, и это вдохновило меня тоже что-нибудь написать.

arm

Обсуждать мы сегодня будем 32-разрядные контроллеры с архитектурой ARM Cortex-M и, в частности, контроллеры от STMicroelectronics (aka ST). Эта статья будет описывать, как начать разработку под указанные контроллеры, доступные среды разработки, используемые библиотеки. Если вам интересна тема Arduino, но 8-битные AVR уже не вставляют, и хочется чего-то большего, то эта статья точно для вас.

Итак, представим, что вы успешно что-то делали на Arduino на 8-битных контроллерах AVR. Может быть, даже какие-то законченные устройства пилили. Но архитектура AVR8, конечно, простая, а контроллеры на ней дешевые, но и функционала она предоставляет не ахти как много, и работает не особо быстро. Что же делать? Правильно: искать более продвинутые альтернативы! И такие, к счастью, есть: это контроллеры на базе архитектуры ARM.

Существует целое семейство вариантов архитектуры ARM, и все они предназначены для разных целей. Это и ARM Cortex-A для приложений вроде полноценных ОС (да, такие часто стоят в ваших смартфонах), и Cortex-R для систем реального времени и систем, где требуется высокий уровень безопасности, и, наконец, Cortex-M - архитектура для микроконтроллеров. Существуют еще более старые архитектуры, но эти три - основные современные. Есть хорошее видео на английском языке, где доступно описываются характеристики и организация этих архитектур.

Мы же будем рассматривать только Cortex-M, так как это та самая архитектура, на базе которой производители типа ST, NXP или Microchip пилят свои контроллеры. Тут, однако, тоже не всё однозначно. Имеется множество ядер, разрабатываемых корпорацией ARM (Cortex-M0, Cortex-M0+, Cortex-M3, Cortex-M7 и т.д.), полный список которых можно найти в Википедии. Ядра эти отличаются наборами поддерживаемых инструкций, организацией конвейера, наличием таких фич, как FPU, MPU и т.д. Лицензия на использование того или иного ядра покупается у ARM конкретным производителем, затем к нему прикручивается различная периферия (таймеры, DMA, всякие интерфейсы типа SPI, USB и I2C и т.д.), всё это лепится на кристалл в чип, и, наконец, вы можете его приобрести и работать с ним.

У меня по воле случая оказалась отладочная плата Open103Z с контроллером STM32F103ZET6.

open103z

Это контроллер на ядре Cortex-M3, которое сейчас весьма популярно среди контроллеров ARM (если не самое популярное). Контроллеры на этом ядре стоят недорого, на том же chipdip.ru самый дешевый стоит порядка 50 рублей, а самый дешевый от STMicroelectronics - 130 рублей. На ebay/aliexpress эти контроллеры стоят еще в два раза дешевле. STM32F103ZET6, который установлен в плату Open103Z, стоит дороже, 500-700 рублей, так как достаточно навороченный, но суть остается та же, и код, написанный под этот контроллер, будет несложно портировать и на другие ST-контроллеры на том же ядре (если, конечно, вы не используете какую-то уникальную периферию, которой нет в более простых вариантах). Есть на ARM Cortex-M3 даже Arduino Due, правда, там стоит контроллер AT91SAM3X8E от Atmel (они же теперь Microchip), но ядро всё то же - Cortex-M3.

Но вернемся к отладочной плате. Раз уж она у меня оказалась, я решил поразбираться с этой темой и написать под её контроллер какой-нибудь код. Раньше я с ARM-микроконтроллерами не работал вообще, поэтому я начал с чтения большого количества статей и документации. Оказалось, что тема разработки под ARM очень популярна в Интернете в целом и в Рунете в частности. Очень многие пилят какие-то свои проекты, пишут мануалы и статьи и обсуждают разные релевантные вопросы на форумах. Я многое из этого прочитал, что-то для себя уяснил, но всё же в голове оставалась какая-то путаница. Миллион сред разработки и компиляторов, какие-то фразы типа CMSIS, SPL, HAL, LL, линкер-скрипты, библиотеки, размазанные по просторам Интернета и т.д. Понимания это всё явно не добавляло. Пришлось копнуть еще глубже, и теперь я готов выложить эти знания в более-менее понятном и, главное, структурированном виде.

Вопрос 1. Какой контроллер мне купить?

Берите любой с ядром Cortex-M3 от STMicroelectronics. Смотрите на наличие периферийных устройств (DMA, таймеры, ШИМ и т.д.) и интерфейсов (SPI, USB, I2C, UART), если вдруг вам нужны какие-то конкретные. Проверьте максимальную рабочую частоту (большинство от ST работают на частоте 72 МГц, что в разы выше частот контроллеров на архитектуре AVR8). STM32F103C8T6 или STM32F103RBT6 - отличные варианты за свои деньги, 64/128 КБ памяти программ, 20 КБ оперативной памяти, куча полезной периферии (DMA, таймеры, watchdog, USB, I2C, SPI, UART, CAN, АЦП, CRC и др.), 72 МГц - максимальная тактовая частота. Из минусов - паять такие контроллеры вручную будет сложно, потому что в DIP-корпусах они недоступны, а доступны только в LQFP, который даже по методике лазерного утюга запаять будет непросто.

Есть много отладочных плат на разных ARM'ах, если не хотите ничего паять самостоятельно. С контроллерами от ST таких тоже очень много. Вот, например, доступные отладочные платы для контроллеров ARM Cortex-M3 на Mouser. На Ebay находятся неплохие бюджетные варианты по запросу "stm32 arm cortex m3 development board" на все тех же STM32F103C8T6 и STM32F103RBT6.

Вопрос 2. Я купил, что дальше-то делать?

Возможно, вам нужен будет еще и программатор. Зависит от типа отладочной платы. Arduino Due его, например, не требует, там всё встроено уже в плату. У меня же оказался STLink/V2 (вы же помните, я говорю преимущественно о контроллерах ST, потому что с ними я имел дело), который работает с любыми контроллерами от STMicroelectronics. Стоит такой программатор две с лишним тысячи рублей, хотя на том же Ebay полно китайских клонов всего по 2.5$. Контроллеры от ST поддерживают отладку и программирование через два интерфейса - JTAG и SWD (Serial Wire Debug). STLink/V2 умеет работать с обоими интерфейсами.

Далее вам нужно определиться с тем, в какой среде программирования вести разработку. Их очень много: Arduino IDE, Eclipse (или даже модификация от ST), Keil MDK ARM (платный, бесплатно можно компилировать только небольшие программы), Visual Studio 2017 + VisualGDB (платный, но стоит своих денег), IAR embedded workbench (EWARM) (платный, бесплатно можно компилировать только небольшие программы), Mbed studio (бесплатная, но специально для разработки под Mbed, про это чуть ниже) и другие. Можете даже писать код в блокноте и компилировать через Makefile, используя напрямую gcc-тулчейн, например, от того же Eclipse (взять можно на гитхабе). Репозиторий Eclipse больше не развивается, качайте отсюда (xpack-dev-tools). Многие среды предоставляют часто какие-то свои встроенные библиотеки для реализации популярного функционала из коробки (USB-устройства всякие, например). Но это не так важно, потому что то же самое предоставляет и STMicroelectronics для своих контроллеров в виде бесплатных библиотек, которые можно подключить к своему проекту. Об этом я подробнее расскажу ниже.

С Eclipse и IAR я не работал, поэтому ничего сказать не могу. Keil MDK позволит вам быстро компилировать код под нужный контроллер, прямо сразу его заливать в плату и отлаживаться. Arduino IDE - примерно то же самое, но подходит только для Arduino Due. Можно установить Arduino Core STM32, тогда в Arduino можно будет писать код для кучи других отладочных плат на ST-контроллерах. Visual Studio 2017 + VisualGDB - очень удобный вариант в том плане, что вы пишете, компилируете, прошиваете и отлаживаете код целиком и полностью в Visual Studio. Работает подсветка синтаксиса и IntelliSense. Думаю, в общем выбор примерно эквивалентен и больше основывается на личных предпочтениях. Я остановился на вариантах с VisualGDB, а также с простым Makefile.

Теперь углубимся в тему разработки под контроллеры именно от STMicroelectronics. Для их контроллеров есть несколько бесплатных утилит, которые помогут в разработке. Во-первых, это STM32CubeProg. Это программатор, поддерживающий все микроконтроллеры от ST. Позволяет писать и читать память контроллера, редактировать ее, менять конфигурационные биты контроллера, прошивать в контроллер скомпилированные ELF-файлы. Утилита пригодится, если вы пожелаете компилировать код через Makefile и обычный gcc, без всяких сред разработки.

stm32cubeprog

Вторая полезная утилита - это STM32CubeMX. Она позволяет через удобный графический интерфейс для любого из контроллеров ST сгенерировать базовый код, который настроит тактовые частоты вашего контроллера, порты ввода-вывода, требуемую периферию, а также создаст файлы проектов под выбранную среду программирования. Из популярных поддерживаются Keil MDK-ARM, IAR EWARM, а также Makefile-проекты. Код генерируется с использованием предоставляемой библиотеки от ST, которая называется HAL. О полезных библиотеках мы поговорим далее. По сути, вы создаете проект через STM32CubeMX, генерируете проект с кодом под требуемую среду программирования, а потом открываете в этой самой среде разработки сгенерированный проект и пишете тот код, который вам нужен, при этом пользуясь тем кодом, который для вас сгенерировал STM32CubeMX. Есть и минусы - при перегенерировании кода (если вдруг у вас поменяются какие-то пожелания к периферии или другим настройкам контроллера) имеющиеся файлы (в которых мог быть и ваш код) перезатрутся. Придется склеивать изменения вручную, если вы уже наменяли что-то в автогенеренных файлах. Кроме того, вы вынуждены будете использовать пресловутую библиотеку HAL, так как весь код, сгенерированный в STM32CubeMX, будет ее плотно использовать.

Настройка периферии контроллера STM32F103C8T6:
STM32CubeMX peripherals

Настройка частот контроллера STM32F103C8T6:
STM32CubeMX clock setup

Обе утилиты, и STM32CubeProg, и STM32CubeMX, - абсолютно бесплатны.

Вопрос 3. Что использовать, чтобы попроще код написать?

Итак, вы установили себе какую-нибудь среду разработки и, вероятно, STM32CubeMX. Следующий вопрос, который у вас возникнет, это: "А какие мне библиотеки применить, чтобы побыстрее запилить что-нибудь работающее?". Итак, что же предлагает разработчику open-source-комьюнити и ST?

  • CMSIS. Это основополагающая библиотека любой разработки под ARM. Разрабатывает её, непосредственно, ARM. Она подходит под вообще любые контроллеры, работающие под управлением ядер ARM, и включает в себя различные универсальные определения, касающиеся этих ядер. Например, управление прерываниями, настройка общих для всех ARM-контроллеров фич типа SysTick-прерываний, управление функционалом защиты страниц памяти (если таковые поддерживаются выбранным контроллером), оптимизированные математические функции и т.д. Эта библиотека работает под любыми компиляторами и в любых средах разработки. Часто среда разработки при создании проекта под какой-нибудь контроллер ARM автоматически подключит нужные заголовочные файлы CMSIS, и вам не придется выкачивать ее вручную (если только вы не хотите использовать самую свежую версию, которую придется вручную скачать с гитхаба). Также CMSIS предлагает производителям контроллеров на базе ядер ARM Cortex-M шаблоны, которые те могут использовать, чтобы встроить в CMSIS собственный код и реализовать в единой манере код для поддержки периферии. STMicroelectronics пользуется этой возможностью постольку-поскольку и предпочитает хреначить собственные библиотеки, не оглядываясь на предлагаемые стандартные описания. CMSIS также имеет шаблоны для создания своих файловых систем, RTOS и т.д. на базе общих интерфейсов.
  • SPL, HAL и LL - это три библиотеки от STMicroelectronics, которые предлагаются для разработки под их контроллеры. Первая, Standard Peripheral Library (SPL), объявлена уже устаревшей. Теперь ST для современных разработок предлагает использовать HAL. Как вы помните, начальный код для инициализации контроллера и требуемой периферии будет для вас сгенерирован утилитой STM32CubeMX как раз с использованием HAL. HAL - это такой своеобразный монстр на языке Си. У нее есть свои плюсы: поддерживаются все контроллеры ST, часть кода для вас генерируется автоматически, упрощается перенос кода с одного контроллера ST на другой, даже если у него другое ядро ARM. Кроме того, в современных контроллерах много функций, но и много аппаратных багов (так называемая errata), с которыми борется эта библиотека, позволяя вам самостоятельно не читать списки этих багов на 40 с лишним страниц, как этот для контроллеров STM32F10xx8 / STM32F10xxB.

    Но есть и минусы, которые многих разработчиков отталкивают: это очень жирная библиотека, как по потреблению памяти, так и по скорости исполнения, не всегда очевидная, часто имеющая баги, с которыми вы будете иметь дело в своих программах. Кроме того, ее использование сразу же усложняет перенос кода на контроллеры других производителей. Как выглядит код, написанный с использованием HAL, легко найти в сети, он действительно часто весьма громоздкий. Вот, например, как выглядит инициализация единственного вывода номер 6 порта ввода-вывода F (это прямо тот код, который автоматически генерирует STM32CubeMX, без изменений). Этот код переключает пин F6 в режим вывода с минимальной частотой (2 МГц максимум) без подтягивающего резистора (open-drain режим) и выводит на него единицу:

    Аж 9 строк кода, 3 из которых - вызовы функций, и одна - макрос, впечатляет. Зато легко переносимо на другие контроллеры от ST (если на том контроллере, куда вы переносите код, тоже есть порт F с выводом #6, хехе).

    Понятное дело, такая громоздкость и тяжеловесность многих разработчиков не устроила, и они стали искать альтернативные решения. STMicroelectronics подтянулись и запилили библиотеку LL (low-level). Она представляет из себя набор файлов на языке Си, содержащих самые базовые и низкоуровневые функции для работы с периферией контроллера. Кому-то нравится, более легковесна, но имеет меньше возможностей. В конечном счете, HAL и сама в некоторых местах сейчас использует макросы и функции из LL. Давайте глянем на пример всё того же кода инициализации пина F6, но теперь на LL (этот код написал уже я сам):

    Мда, на 1 строку больше стало... Но, по крайней мере после компиляции сорса бинарного кода меньше становится, судя по отзывам тех, кто плотно пользовался HAL и LL.

    Вы можете спросить: а где же взять-то эти библиотеки? А нигде. Они распространяются вместе с STM32CubeMX. Ставите его на свой компьютер, и у вас появляются файлы этих библиотек. Генерируете проект для нужного контроллера - в указанную папку копируются нужные файлы этих библиотек для выбранного контроллера. Некоторые разработчики куда-нибудь перезаливают файлы библиотек для того или иного ядра, но никто не дает гарантии, что там они последней версии. STM32CubeMX идет еще дальше и позволяет вам включить в проект сразу целую FreeRTOS, файловую систему FAT или файлы для работы с USB. Думаю, тут заодно стоит упомянуть и STM32 USB Device Library. Это библиотека, предоставляющая функции для работы с интерфейсом USB, которая также поставляется в виде шаблонов с STM32CubeMX, а потом используется для генерации начального кода вашего проекта, если вы выбрали какие-то опции, касающиеся USB.

  • В контексте контроллеров от ST стоит упомянуть заголовочные файлы, которые сами ST называют "CMSIS Device Peripheral Access Layer Header File". Это файлы, которые содержат все необходимые определения (#define), структуры и константы для всех контроллеров от ST, совместимые с библиотекой CMSIS. Технически, достаточно взять файлы CMSIS и один заголовочный файл от ST с описанием периферии, и вы уже сможете писать низкоуровневый код под выбранный контроллер. Я, честно говоря, и пошел по такому пути в своих первых проектах, чтобы максимально разобраться в архитектуре используемого железа. Да, код не такой переносимый получится (что, кстати, спорно), но для хобби и не требуется крутой переносимости. Где же взять эти заголовочные файлы? Как вы уже, вероятно, догадались, их генерирует в ваш проект STM32CubeMX, да. Но их, конечно, можно взять и на неофициальных страницах гитхаб наподобие этой. Данные файлы подкладываются в каталог с CMSIS (папка Devices, предназначенная как раз для подобных заголовочных файлов от производителей контроллеров):
    cmsis files

    Что ж, давайте в лучших традициях перепишем снова всё тот же код инициализации пина PF6, не упуская ни одного действия из изначально сгенерированного кода, но теперь используя только заголовочный файл для нужного контроллера (для моей отладочной платы это stm32f1xx.h, который, в свою очередь, подключает stm32f1xe.h) и CMSIS:

    Всего 5 строк кода, но без более детальных комментариев уже будет не обойтись. Тут мы видим, что есть странная строка, начинающаяся с [[maybe_unused]]. Это как раз одна из багофич многих контроллеров STM32: когда вы включаете периферию (мы включаем тактирование порта F), нужно следующей командной считать значение регистра конфигурации тактирования периферии (APB2ENR в нашем случае). Иначе есть вероятность, что тактирование будет не сразу включено при выполнении последующих инструкций. А это нам необходимо, так как сразу после включения тактирования мы начинаем писать в регистры порта F. Если их тактирование будет выключено, значения в них попросту не запишутся. При использовании библиотек HAL или LL этот непонятный код спрятан внутрь макроса или функции, которую вы вызываете (__HAL_RCC_GPIOF_CLK_ENABLE или LL_APB2_GRP1_EnableClock, соответственно), и вам не нужно об этом помнить.

    Я уже не говорю о макросах GPIO_CRL_MODE6_1 и GPIO_CRL_CNF6_0, которые совсем не добавляют вашему коду наглядности. Без даташита хрен поймешь, что тут происходит:

    datasheet gpio registers

    Наконец, написать что-то более-менее сложное в такой манере будет малореально, только если у вас не очень много свободного времени. HAL и LL наряду с STM32 USB Device Library, хоть и громоздкие, но содержат множество сложной логики (например, реализацию протоколов USB HID или реализацию взаимодействия, скажем, с SPI через DMA). Эту логику уже до вас написали и отладили, чтобы можно было ее использовать из коробки.

    Я, эксперментируя с разными вариантами написания кода, соорудил даже свою небольшую библиотеку, на которой всё тот же простой код выглядит так:

    Вот тут уже всё понятно, проверяется на этапе компиляции языком C++ и, в целом, вообще не привязано к какому бы то ни было контроллеру. Главное, чтобы у него было порт F с выводом номер 6. Но библиотеку эту я не выложу, так как она в стадии наколеночной разработки в хобби-проектах.

  • Сторонние разработчики пилят кастомные библиотеки под Cortex-M3. Одна из самых известных библиотек - это libopencm3. В ней есть многие высокоуровневые фичи, она совместима с большим количеством контроллеров на ядре Cortex-M3 от разных производителей, она активно развивается, но пока что является work-in-progress, что означает, что контракты интерфейсов библиотеки могут пока радикально меняться. Я эту библиотеку не щупал, но, говорят, это весьма солидный вариант. В качестве классического уже упражнения, изобразим на ней всё тот же код для инициализации вывода PF6:

    Думаю, тут всё просто и наглядно, даже комментарии не нужны!

  • Стоит упомянуть еще одну библиотеку для работы с USB, на которую я наткнулся, - libusb_stm32. Совместима со многими контроллерами от ST, достаточно компактна и быстра, не заброшена. Код неплохой и легко читается, можно использовать для изучения премудростей работы с USB на контроллерах от STMicroelectronics.
  • Набирает популярность официальная операционная система от ARM Mbed. Она содержит в себе как код операционной системы, так и код работы с периферией разных контроллеров, а также с различными аппаратными устройствами (такими, как LCD-дисплеи). Она бесплатная и проще всего компилируется под различные отладочные платы с контроллерами на базе Cortex-M.

Вопрос 4. Библиотеки выбрали, а дальше, как собирать и прошивать?

Библиотеки выбрали, теперь пишем код! Собираем его в выбранной среде программирования. С этим сложностей возникнуть не должно, так как процесс сборки все среды максимально упрощают. Остановлюсь только поподробнее на том, как собирать проект с использованием Makefile. Если вы генерировали Makefile-проект с использованием STM32CubeMX, то тут тоже всё просто - с помощью команды make запускаете сборку, и на этом всё. Если же вы вручную пишете Makefile, либо, например, желаете пересобрать ее под другой контроллер, то вам потребуется найти/заменить так называемый линкер-скрипт для выбранного контроллера, а также файл с кодом инициализации. Справедливости ради, все среды программирования при сборке используют такие скрипты, только вот они находят подходящие и конфигурируют их за вас.

Начнем с линкер-скриптов. Это файлы, как правило, с расширением ld. Для моей отладочной платы такой скрипт называется STM32F103ZETx_FLASH.ld. Взять его можно всё из того же проекта от STM32CubeMX, либо найти в неофициальных репозиториях на гитхабе. Можно также воспользоваться генератором этих скритов из той же библиотеки libopencm3, или же, в конце-концов, написать самому, благо, в Интернете много инструкций на эту тему. Итак, что же это за скрипты такие, и почему они не нужны для AVR8? Это набор указаний для линкера о том, куда какой код раскладывать. Так как в STM32 карта памяти сложнее, чем в 8-битных AVR, то при прошивке нужно знать, куда положить таблицу векторов прерываний, куда - данные программы, куда - неинициализированные данные. Вот линкер-скрипт об этом и сообщает. Написать его самому можно, изучив карту памяти для ядра Cortex-M3:

datasheet memory map

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

Осталось взглянуть на код инициализации. Это, как правило, низкоуровневый код, написанный на ассемблере, который содержит таблицу прерываний и строки, которые выполняются после включения или сброса контроллера. Современные среды разработки его также включают автоматически для нужного контроллера, но для общего развития (или на тот случай, если вы пожелаете собирать проект с помощью Makefile с нуля) я о нем кратко расскажу. Для моей платы этот код содержится в файле с именем startup_stm32f103xe.s, и взял я его всё из того же STM32CubeMX, да. Он содержит таблицу векторов прерываний для конкретного контроллера (они у всех разные в зависимости от наличия той или иной периферии). Кроме того, там есть код очистки неинициализированных данных, код, копирующий инициализированные данные из памяти программ в правильные адреса оперативной памяти, и код, выполняющий конструкторы и деструкторы глобальных объектов C++. Словом, всё, что необходимо для того, чтобы дальше вы в своей программе (которую вызовет этот самый код инициализации) могли безболезненно использовать возможности языков C и C++ и их стандартных библиотек.

О прошивке кода я уже писал. Используете среду разработки - прошивайте через нее, она точно это поддерживает. Используете Makefile - прошивайте через STM32CubeProg или, как вариант, через OpenOCD.

Вопрос 5. Собрали, прошили, запустили. А как отладить теперь?

Если используете среду разработки, то, как вы уже поняли, она будет поддерживать отладку из коробки. Возможно, придется это немного настроить, но это делается один раз. Вот, например, как выглядят настройки для отладки в VisualGDB (который использует OpenOCD в качестве бэкенда):

debug settings in visualgdb

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

В случае с проектом на Makefile есть вариант использовать всё тот же OpenOCD или же gdb напрямую.

Вопросы заданы, вопросов больше нет!

На этой ноте я подхожу к завершению рассказа, так как это всё, что необходимо знать, чтобы начать разработку под контроллеры ARM (в частности, от STMicroelectronics на ядре Cortex-M3, но не только!). Надеюсь, информация будет полезна тем, кто не был знаком с этими ядрами, а тем, кто был, позволит расширить кругозор. Желаю счастливого кодинга и поменьше ошибок, и готов ответить на ваши вопросы в комментариях.

Знакомимся с микроконтроллерами на ядрах ARM Cortex-M: 3 комментария

  1. Dx,хотелось бы по-больше ассемблера.Например какой ни будь софт,может троян по типу стиллера с sqlite3 подержкой или чего то еще.Было б приятно увидить подобное.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *