Вы находитесь на странице: 1из 1

КАК СТАТЬ АВТОРОМ Как стать домашним админом и не свести семью с ума

Все потоки Разработка Администрирование Дизайн Менеджмент Маркетинг Научпоп Войти Регистрация

ezh213b 24 марта 2014 в 12:01


ЧИТАЮТ СЕЙЧАС
Начинаем изучать Cortex-M на примере STM32
Программирование микроконтроллеров Судьба предателя, угнавшего
новейший МиГ-25 в Японию
Из песочницы Tutorial
130k 718

Данная статья является первой в планируемом цикле статей по изучению программирования Обратная сторона Open Source-славы:
микроконтроллеров. Изучая различные материалы я отметил, что практически все они начинаются с как угрожают автору curl
того, что новичку предлагается скачать (или использовать идущую со средой разработки) библиотеку 34,1k 86
для работы с периферийными устройствами и использовать ее для написания своей первой программы
(обычно мигание светодиодом). Работа сознания: что такое пятна
Габора?
Меня это сильно удивило. Если верить данным статьям, для программирования не обязательно даже 45,3k 49
читать документацию к программируемому контроллеру. Меня же учили премудростям «железного
программирования» совершенно иначе. Глава Microsoft заявил об имеющихся
доказательствах причастности России
к хакерской атаке на SolarWinds
В этой статье, путь от фразы «Да, я хочу попробовать!» до радостного подмигивания светодиода, будет
3,9k 14
значительно длиннее чем у других авторов. Я постараюсь раскрыть аспекты программирования
микроконтроллеров, которые прячутся за использованием библиотечных функций и готовых примеров.
5 книг Стругацких
Если вы намерены серьезно изучать программирование микроконтроллеров данная статья для вас.
6,4k 17
Возможно, она может заинтересовать и тех, кто вдоволь наигрался с Arduino и хочет получить в свои
руки все аппаратные возможности железа.
Мир JavaScript в 2021 году
Выбор микроконтроллера 12,2k 10

Как хабраюзеры сервисами банков


Многие могут сказать, что начинать изучение микроконтроллеров лучше с AVR, PIC, 8051 или чего-то
пользовались и остались недовольны
еще. Вопрос многогранный и спорный. Я знаю достаточно примеров, когда люди изучив Cortex-M,
Мегапост
программировали AVR, ARM7 и т.д. Сам же я начинал с Cortex-M3. Если перед вами стоит определенная
задача, в интернете достаточно много информации со сравнением различных типов
микроконтроллеров и решаемых с их помощью задач. На хабре этот вопрос тоже поднимался,
например тут. Редакторский дайджест
Присылаем лучшие статьи раз в месяц
Будем считать, что с типом микроконтроллера мы разобрались. Но на рынке представлен огромнейший
спектр различных модификаций от разных производителей. Они отличаются по множеству параметров Электропочта

— от размера флеш памяти до количества аналоговых входов. Для каждой задачи выбор стоит
производить индивидуально. Ни каких общих рекомендаций тут нет и быть не может. Отмечу лишь, что
стоит начинать изучение с МК производителей имеющих как можно больший ассортимент. Тогда, при
выборе МК для определенной задачи достаточно велик шанс, что из представленного ассортимента вам
что-нибудь да подойдет.

Я остановил свой выбор на STM32 (хотя и считаю, что лучше начинать изучение с МК от
TexasInstruments — очень грамотно составлена документация), потому что они широко распространены
среди российских разработчиков электроники. При возникновении проблем и вопросов вы сможете
без труда найти решения на форумах. Еще одним плюсом является богатый выбор демонстрационных
плат как от производителя, так и от сторонних организаций.

Что необходимо для изучения?


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

Сам я использую демонстрационную плату STM3220G-EVAL и программатор J-Link PRO. Но для


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

Все примеры будут именно для отладочной платы STM32F4DISCOVERY. На данном этапе нам будет
совершенно не важно, что этой плате стоит МК на базе ядра Cortex-M4. В ближайшее время мы не
будем использовать его особенности и преимущества над Cortex-M3. А как там будет дальше —
посмотрим.

Если у вас есть в наличии любая другая плата на базе STM32F2xx/STM32F4xx, вы сможете работать с ней.
В изложении материала я постараюсь максимально подробно описывать почему мы делаем именно так,
а не иначе. Надеюсь ни у кого не возникнет проблем с переносом примеров на другое железо.

Среда разработки
Как уже неоднократно упоминалось, для ARM микроконтроллеров существует достаточное количество
сред разработки, как платных так и не очень. И снова хочется опустить полемику по этому поводу. Я
использую IAR Embedded Workbench for ARM 6.60. Все примеры будут именно в этой среде. Если вам по
душе (или в вашей организации используется) что-то другое (Keil, Eclipse, CCS, CooCoc и т.д.) то это вам
тоже не очень помешает. На особенности, связанные именно со средой разработки, я буду обращать
отдельное внимание.
Почему платная среда разработки?

Процесс установки я описывать не буду.

С чего начать?
Создание проекта
Для начала создадим пустой проект. IAR позволяет создать проекты на ASM, C и C++. Мы будем
использовать C.

Перед нами появится пустой проект с main файлом.

Теперь необходимо настроить проект для начала работы с «нашим» МК и отладчиком. На плате
STM32F4DISCOVERY установлен MK STM32F407VG . Его необходимо выбрать в свойствах проекта
(General Options->Target->Device):

При выборе целевого программируемого процессора происходит загрузка его описания, что дает
широкие возможности для отладки (об этом будет идти речь ниже). Кроме того, автоматически
присоединяется конфигурационный файл с описанием доступного адресного пространства для линкера.
Если будет необходимо, мы затронем тему конфигурационного файла линкера в следующих статьях.

После этого необходимо настроить отладчик. Отладка программы происходит непосредственно «в


железе». Производится это с помощью JTAG отладчика. Более подробнее ознакомиться с тем, как это
происходит можно на Википедии. На плату STM32F4DISCOVERY интегрирован отладчик ST-LINK/V2. Для
работы с отладчиком необходимо выбрать его драйвер в меню Debugger->Setup->Driver. Так же
необходимо указать, что отладка должна производиться непосредственно в железе. Для этого
необходимо поставить флаг Debugger->Download->Use flash loader(s)

Для тех, кто увидел слово Simulator

Теперь проект готов для работы (программирования, заливки и отладки).

«ТЗ» для первого проекта


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

Не будем отходить от классики. Первым проектом будет мигающий светодиод. Благо на плате их
предостаточно.Что же это означает с точки зрения программирования? Первым делом необходимо
изучить принципиальную схему демонстрационной платы и понять как «заводится» светодиод.
User manualдоступен на сайте производителя. В данном описании даже есть отдельный раздел про
светодиоды на плате -4.4 LEDs. Для примера, будем использовать User LD3. Найдем его на схеме:

Простейший анализ схемы говорит о том, что для того, что бы «зажечь» светодиод необходимо на пин
МК подать «1» (которая для данного МК соответствует 3.3В). Выключение производится подачей на этот
пин «0». На схеме этот пин обозначается PD13 (это, наверное, самая важная информация из этого
документа).

В итоге, мы можем написать «ТЗ» для нашей первой программы:


Программа для МК должна переводить состояние пина МК PD13 из состояния «0» в состояние «1» и
обратно с некоторой периодичностью, различимой для человеческого глаза (важное замечание, если
моргать светодиодом слишком часто глаз может этого не различить).

Прежде чем приступать к программированию, или немного теории


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

Начнем с того, что любой МК включает ядро, память и периферийные блоки. Думаю, что с памятью
пока все понятно. Упомяну лишь, в STM32 есть флеш память в которой хранится программа МК (в
общем случае это не верное утверждение, программа может храниться во внешней энергонезависимой
памяти, но пока это опустим) и другие данные, в том числе и пользовательские. Так же есть SRAM —
оперативная память.

Ядро — часть микроконтроллера, осуществляющая выполнение одного потока команд. В нашем МК тип
ядра — Cortex-M4. Ядро МК можно сравнить с процессором в ПК. Оно умеет только выполнять
команды и передавать данные другим блокам (в этом сравнении не учитываются процессоры с
интегрированными графическими ускорителями).
При этом производитель МК не разрабатывает ядро. Ядро покупается у компании ARM Limited. Главное
отличие между различными МК — в периферии.

Периферийные блоки — блоки осуществляющие взаимодействие с «внешним миром» или


выполняющие специфические функции, недоступные ядру МК. Современные МК (в том числе и STM32)
содержат огромный спектр периферийных блоков. Периферийные блоки предназначены для решения
различных задач, от считывания значения напряжения с аналогового входа МК до передачи данных
внешним устройствам по шине SPI.
В отличии от ядра МК периферийные блоки не выполняют инструкции. Они лишь выполняют команды
ядра. При этом участие ядра при выполнении команды не требуется.
Пример

Взаимодействие ядра с периферийным блоком


Взаимодействие ядра МК с периферийным блоком осуществляется с помощью спецрегистров (есть еще
взаимодействие через механизм прерываний и DMA, но об этом в следующих постах). С точки зрения
ядра это просто участок памяти с определенным адресом, вот только на самом деле это не так.
Запись данных в спецрегистр эквивалентна передаче команды или данных периферийному блоку.
Считывание — получение данных от блока или считывание его состояния. Описание периферийных
блоков и их спецрегистров занимает львиную долю описания МК.

ВАЖНО: После записи данных в спецрегистр и последующем чтении вы можете получить совершенно
иные данные. Например, передача данных блоку UART для отправки, и считывание данных, полученных
блоком от внешнего устройства, осуществляется с помощью одного и того же регистра.

Спецрегистры обычно разделены на битовые поля. Один (или несколько) бит управляют определенным
параметром периферийного блока, обычно независимо. Например, разные биты одного регистра
управляют состоянием разных выходов МК.

Вспоминаем С
Если вы гуру в языке C, то можете смело пропускать данный раздел. Он предназначен в первую очередь
для тех, кого учили (или ктоучился сам) программировать для ПК. Опыт показывает, что люди часто не
помнят важных команд. Здесь я вкратце напомню про побитовые операции и работу напрямую с
памятью по ее адресу.

Запись данных по адресу в памяти

Предположим, что читая описание периферийного блока, мы поняли, что для его корректной работы
необходимо записать в него число 0x3B. Адрес спецрегистра 0x60004012. Регистр 32-битный.
Если вы сразу не знаете как это сделать, попробую описать цепочку рассуждений для получения
правильной команды.

Значение 0x60004012 есть не что иное, как значение указателя на ячейку памяти. Нужно именно это и
указать в нашей программе, тоесть сделать преобразование типов согласно синтаксису языка C:

(unsigned long*)(0x60004012)

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

*(unsigned long*)(0x60004012) = 0x3B;

Установка произвольных бит в 1

Предположим, что необходимо установить «1» в 7 и 1 биты по адресу 0x60004012, при этом не изменив
значение всех остальных бит в регистре. Для этого необходимо использовать бинарную операцию |.
Сразу приведу правильный ответ:

*(unsigned long*)(0x60004012) |= 0x82;

Обратите внимание на 2 факта. Биты считаются с нулевого, а не с первого. Данная операция на самом
деле занимает неменее 3 тактов — считывание значения, модификация, запись. Иногда это не
допустимо, поскольку между считыванием и записью значение одного из бит, которые нам запрещено
изменять, могло быть изменено периферийным блоком. Незабывайте про эту особенность, иначе могут
полезть баги, которые крайне сложно отловить.

Установка произвольных бит в 0

Предположим, что необходимо установить «0» в 7 и 1 биты по адресу 0x60004012, при этом не изменив
значение всех остальных бит в регистре. Для этого необходимо использовать бинарную операцию &.
Сразу приведу правильный ответ:

*(unsigned long*)(0x60004012) &= 0xFFFFFF7D;

Или его более простою запись (не переживайте за лишнюю операцию, компилятор все заранее
посчитает даже при минимальной оптимизации):

*(unsigned long*)(0x60004012) &= (~0x82);

Некоторые особенности программ для МК


Здесь я постараюсь описать некоторые особенности программ для МК, которые важно помнить. Вещи
достаточно очевидные, но все же.
У программы нет конца
В отличии от большинства программ для ПК, программа для МК не должна заканчиваться, НИКОГДА! А
что собственно должен будет делать МК после завершения вашей программы? Вопрос, практически,
риторический. Поэтому не забываем убедиться в том, что вы не забыли вечный цикл. При желании,
можно перевести МК в режим сна.
Пользуйтесь целочисленными переменными
Не смотря на то, что мы используем МК с ядром Cortex-M4, который аппаратно выполняет операции
над числами с плавающей точкой, советую вам отказаться от их использования. В МК без поддержки
таких операций время вычислений будет просто огромным.
Откажитесь от динамического выделения памяти
Это только совет. Причина проста — памяти мало. Я не раз встречался с библиотеками, в которых были
«медленные утечки» памяти. Было очень неприятно, когда после нескольких недель стабильной работы
МК зависал с ошибкой. Лучше заранее продумать архитектуру своей программы так, чтобы не
пришлось использовать динамическое выделение памяти.
Если же все-таки хочется использовать — внимательно изучите работу менеджера памяти или пишите
свой.

Приступаем к работе!
Работа над программой для МК всегда начинается с чтения документации. Для нашего МК Reference
manual доступен на сайте производителя. Страниц много, но все читать пока не нужно. Как уже было
сказано, большую часть документации составляет описание периферийных блоков и их регистров. Так
же хочу обратить внимание на то, что этот Reference Manual написан не для одного МК, а для
нескольких линеек. Это говорит о том, что код будет переносим при переходе на другие МК в этих
линейках (если конечно не пытаться использовать периферийные блоки которых нет в используемом
МК).

В первую очередь необходимо определиться с какими блоками предстоит работать. Для это достаточно
изучит разделы Introduction и Main features.

Непосредственное управление состоянием пинов МК осуществляется с помощью блока GPIO. Как


указано в документации в МК STM32 может быть до 11 независимых блоков GPIO. Различные
периферийные блоки GPIO принято называть портами. Порты обозначаются буквам от A до K. Каждый
порт может содержать до 16 пинов. Как мы отметили ранее, светодиод подключается к пину PD13. Это
означает, что управление этим пином осуществляется периферийным блоком GPIO порт D. Номер пина
13.

Ни каких других периферийных блоков на это раз нам не понадобится.

Управление тактированием периферийных блоков


Для снижения электропотребления МК практически все периферийные блоки после включения МК
отключены. Включение/выключение блока производится подачей/прекращением подачи тактового
сигнала на его вход. Для корректной работы, необходимо сконфигурировать контроллер тактового
сигнала МК, чтобы необходимому периферийному блоку поступал тактовый сигнал.
Важно:Периферийный блок не может начать работу сразу после включения тактового сигнала.
Необходимо подождать несколько тактов пока он «запустится». Люди, использующие библиотеки для
периферийных устройств, зачастую даже не знают об этой особенности.

За включение тактирования периферийных блоков отвечают регистры RCC XXX peripheral clock enable
register.На месте XXX могут стоять шины AHB1, AHB2, AHB3, APB1 и APB2. После внимательного изучения
описания соответствующих регистров, можно сделать вывод о том, тактирование периферийного блока
GPIOD включается установкой «1» в третий бит регистра RCC AHB1 peripheral clock enable register
(RCC_AHB1ENR):

Теперь необходимо разобраться с тем, как узнать адрес самого регистра RCC_AHB1ENR.

Замечание: Описание системы тактирования МК STM32 достойно отдельной статьи. Если у читателей
возникнет желание, я подробнее освещу этот раздел в одной из следующих статей.

Определение адресов спецрегистров


Определение адресов спецрегистров необходимо начинать с чтения раздела Memory map в Reference
manual. Можно заметить, что каждому блоку выделен свой участок адресного пространства. Например,
для блока RCC это участок 0x4002 3800 — 0x4002 3BFF:

Перейдя по ссылке к Register map блока RCC находим строчкку с интересующим нас регистром
RCC_AHB1ENR:

Для получения адреса регистра, необходимо к начальному значению адресного пространства блока
RCC прибавить Addr. offset нужного регистра. Addres offset указывается и в описании регистра (см.
скриншот выше).

В итоге, мы определили адрес регистра RCC_AHB1ENR — 0x4002 3830.

Блок GPIO
Для общего ознакомления с блоком GPIO я настоятельно рекомендую полностью прочитать
соответствующий раздел Reference Manual. Пока можно не особо обращать внимание на Alternate mode.
Это оставим на потом.

Сейчас же наша задача научиться управлять состоянием пинов МК. Перейдем сразу к описанию
регистров GPIO.

Режим работы
В первую очередь необходимо установить режим работы 13 пина порта D как General purpose output
mode, что означает что блок GPIO будет управлять состоянием пина МК. Управление режимом работы
пинов МК производитсяс помощью регистра GPIO port mode register (GPIOx_MODER) (x = A..I/J/K):

Как видно из описания для совершения требуемой нам настройки необходимо записать значение 01b в
26-27 биты регистра GPIOx_MODER. Адрес регистра можно определить тем же методом, что описан
выше.

Настройка параметров работы выходных пинов порта GPIO


Блок GPIO позволяет применить дополнительные настройки для выходных пинов порта. Данные
настройки производятся в регистрах:
GPIO port output type register (GPIOx_OTYPER) — задается тип выхода push-pull или open-drain

GPIO port output speed register (GPIOx_OSPEEDR) — задается скорость работы выхода

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

Установка значения на пине МК


Наконец-то мы подошли к моменту управления состоянием выхода МК. Для утановки выходного
значения на определенном пине МК есть два метода.

Используем регистр GPIO port bit set/reset register (GPIOx_BSRR)

Запись «0» или «1» в биты 0-16 приводят к соответствующему изменению состояния пинов порта. Для
того, чтобы установить определенное значение на выходе одного или нескольких пинов МК и не
изменить состояния остальных, необходимо будет пользоваться операцией модификации отдельных
бит. Такая операция выполняется не менее чем за 3 такта. Если же необходимо в часть битов записать 1,
а в другие 0, то понадобится не менее 4 тактов. Данный метод предпочтительнее всего использовать
для изменения состояния выхода на противоположное, если его изначальное состояние не известно.

GPIO port bit set/reset register (GPIOx_BSRR)

В отличии от предыдущего метода, запись 0 в любой из битов данного регистра не приведет ни к чему
(да и вообще, все биты write-only!). Запись 1 в биты 0-15 приведет к установке «1» на соответствующем
выходе МК. Запись 1 в биты 16-31 приведет к установке «0» на соответствующем выходе МК. Этот метод
предпочтительнее предыдущего, если необходимо установить определенное значение на пине «МК», а
не изменить его.

Зажигаем светодиод!
Найдя адреса всех необходимых регистров, можно написать программу, которая включает светодиод:

void main()
{
//Enable port D clocking
*(unsigned long*)(0x40023830) |= 0x8;

//little delay for GPIOD get ready


volatile unsigned long i=0;
i++; i++; i++;
i=0;

//Set PD13 as General purpose output


*(unsigned long*)(0x40020C00) = (*(unsigned long*)(0x40020C00)& (~0x0C000000)) | (0x0400000
0);

//Turn LED ON!


*(unsigned long*)(0x40020C14) |= 0x2000;

while(1);
}

Можно компилировать (Project->Compile) и заливать (Project->Download->Download active application).


Или запустить отладку (Project->Dpwnload and Debug) и начать выполнение (F5).
Светодиод загорелся!

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

void main()
{
//Enable port D clocking
*(unsigned long*)(0x40023830) |= 0x8;

//little delay for GPIOD get ready


volatile unsigned long i=0;
i++; i++; i++;
i=0;

//Set PD13 as General purpose output


*(unsigned long*)(0x40020C00) = (*(unsigned long*)(0x40020C00)& (~0x0C000000)) | (0x0400000
0);

while(1)
{
//Turn LED ON
*(unsigned long*)(0x40020C14) |= 0x2000;
//Delay
for( i=0; i<1000000 ;++i );
//Turn LED OFF
*(unsigned long*)(0x40020C14) &= ~0x2000;
//Delay
for( i=0; i<1000000 ;++i );
}
}

Значение 1000000 в задержке подобрано экспериментально так, чтобы период мигания светодиода был
различим глазом, но и не был слишком велик.

Оптимизируем алгоритм
Минусом выбранного подхода миганием светодиодом является то, что ядро МК большую часть
времени проводит в пустых циклах, хотя мог бы заниматься чем-нибудь полезным (в нашем примере
других задач нет, но в будущем они появятся).

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

void main()
{
//Enable port D clocking
*(unsigned long*)(0x40023830) |= 0x8;

//little delay for GPIOD get ready


volatile unsigned long i=0;
i++; i++; i++;
i=0;

//Set PD13 as General purpose output


*(unsigned long*)(0x40020C00) = (*(unsigned long*)(0x40020C00)& (~0x0C000000)) | (0x0400000
0);

while(1)
{
i++;

if( !(i%2000000) )
{
//Turn LED ON
*(unsigned long*)(0x40020С14) |= 0x2020;
}
else if( !(i%1000000) )
{
//Turn LED OFF
*(unsigned long*)(0x40020С14) &= ~0x2000;
}
}
}

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

Немного об отладке
IAR позволяет осуществлять отладку приложения непосредственно в железе. Все выглядит практически
так же, как и отладка приложения для ПК. Есть режим пошагового выполнения, входа в функцию,
просмотр значения переменных (В режиме отладки View->Watch->Watch1/4).

Но помимо этого, присутствует возможность просмотра значений регистров ядра, спецрегистров


периферийных блоков (View->Register) и т.п.
Я настоятельно рекомендую ознакомиться с возможностями дебаггера во время изучения
программирования МК.

Несколько слов в заключение


Возможно, многие скажут, что ручное прописывание адресов в программе это не правильно, поскольку
производитель предоставляет файлы с определениями регистров и битовых полей, библиотеки для
работы с периферией и другие инструменты, облегчающие жизнь разработчику. Я с этим полностью
согласен, но все равно считаю, что первые шаги в программировании МК необходимо делать
перекапывая документацию к вручную, самостоятельно определяя необходимые регистры и битовые
поля. В дальнейшем этим можно не пользоваться, но уметь нужно обязательно.
Приведу лишь несколько причин для этого утверждения:
В библиотеках от производителя иногда встречаются ошибки! Я один раз чуть не сорвал срок
проекта из-за этого. Несколько раз перепаивал чип, думая, сто повредил кристалл при пайке (до
этого такое случалось). А проблема заключалась в том, что в библиотеке был неверно прописан
адрес спецрегистра. Обычно такое случается с МК или линейками МК только вышедшими на
рынок.

Библиотеки для работы спериферией некоторых производителей не реализуют всех возможностей


периферийных блоков. Особенно этим грешилb Luminary Micro, которых в последствии выкупили
TI. Приходилось писать инициализацию периферии вручную.

Многие привыкают начинать программирование МК с изучения примеров. Я считаю, что сперва


необходимо определиться с тем, что позволяет реализовать МК. Это можнопонять только прочитав
документацию. Если чего-то нет в примерах, это не значит, что железоэто не поддерживает.
Последний пример — аппаратная поддерка PTP STM32. В сети, конечно, можно кое-что найти, но
это не входит в стандартный набор от производителя.

Драйверы периферийных блоков некоторых производителей настолько не оптимизированы, что на


переключение состояния пина средствами библиотеки тратится до 20 тактов. Это непозволительная
роскошь для некоторых задач.

Спасибо всем, кто прочитал мой пост, получилось значительно больше чем я ожидал в начале.
Жду ваших комментариев и аргументированной критики. Если у прочитавших возникнет желание —
постараюсь продолжить цикл статей. Возможно у кого-то есть идеи по поводу тем, которые стоило бы
осветить — я был бы рад их услышать.

Теги: STM32, Cortex-M, микроконтроллеры

Хабы: Программирование микроконтроллеров

+24 448 259k 39 Поделиться

29,0 0,0
Карма Рейтинг

@ ezh213b
Пользователь

ПОХОЖИЕ ПУБЛИКАЦИИ МИНУТОЧКУ ВНИМАНИЯ

2 октября 2018 в 12:14

Как защититься от переполнения стека (на Cortex M)?


+59 19,1k 164 66

5 июня 2017 в 14:24 Турбо


Как сделать context switch на STM32
+27 18,9k 94 22 Какой IT-работодатель всех сильней?
Покажем и докажем
22 апреля 2014 в 00:05

Начинаем изучать Cortex-M на примере STM32, часть 2


+27 106k 295 25

Опрос
СРЕДНЯЯ ЗАРПЛАТА В IT

Вошёл в IT – а дальше что? Отвечают


120 000 ₽/мес. хабраюзеры

Средняя зарплата по всем IT-


специализациям на основании 3 227
анкет, за 1-ое пол. 2021 года

Узнать свою зарплату

Комментарии 39 ЧТО ОБСУЖДАЮТ

Ariman 24 марта 2014 в 12:11 +8 Сейчас Вчера Неделя


Регистры у F4xx и F1xx отличаются, у тех же GPIO, допустим. Настраиваются по-разному, у F1xx режим с
настройками упиханы в общий регистр, у F4xx — отдельные регистры. Цензура в интернете. Надо что-то
Ремэп тоже сильно отличается, у F1xx это один регистр с битами вида «ремэпнуть таймер1», у F4xx — делать
полноценный ремэпер, где можно на конкретный пин задать конкретную периферию. 10,8k 91

Поэтому я бы не стал говорить что не важно, что юзать — F1xx или F4xx, у начинающих запросто могут
Судьба предателя, угнавшего
вылезти проблемы, оттого, что, скажем, они пишут код как для F1xx, пытаются его использовать на F4xx, но не новейший МиГ-25 в Японию
включили мэпинг периферии.
130k 718

P.S. Магические числа в коде — это адЪ. Хоть бы константы именованные юзали.
Pet-проект для джуна. Или зачем и как
выбрать pet project. (+личный опыт)
3,6k 2
ezh213b 24 марта 2014 в 13:16 0

Для этого я специально указал, что лучше использовать F2 или F4:


Джон Кармак предложил способ
Если у вас есть в наличии любая другая плата на базе STM32F2xx/STM32F4xx, вы сможете работать с борьбы с дефицитом видеокарт и
ней. игровых консолей нового поколения
31,5k 351

Вообще говоря, складывается впечатление, что на F1 ST «обкатывали» технологии и накапливали


проблемы и баги. F2 сильно отличается от F1, а вот последующие уже очень похожи. runBlocking против приложения: как
К сожалению, невозможно было выбрать один универсальный МК. Также не вижу смысла писать оправить приложение в нокаут из-за
обязательные требования по какой-то определенной отладочной плате. невнимательности
Интересно
Моей задачей было объяснить принцип, по которому стоит начинать писать программу, а не перечислить
конкретные регистры. Может быть получилось не очень хорошо.

На счет магических чисел я тоже вас поддерживаю, но считаю, на первых шагах код должен выглядеть
именно так. Отчасти из-за того, что при желании воспользоваться «готовым» куском кода вам придется
лезть в описание регистров МК.

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

Ariman 24 марта 2014 в 13:26 +8

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


Во-первых, если вы так хотите, чтобы обучающиеся полезли в описание регистров, то какая разница,
напишите вы

//Enable port D clocking


*(unsigned long*)(0x40023830) |= 0x8;

или

#define RCCAHB1_ENR (unsigned long*)0x40023830


//Enable port D clocking
*RCCAHB1_ENR |= 0x8;

По-прежнему не используется библиотека, по-прежнему разыменуется адрес регистра, только теперь


код не выглядит так отвратительно и не прививает «говнокодерский» стиль с магическими числами.

Во-вторых, каким образом ваш код больше стимулирует к изучению документации, чем код с
использованием дефайнов от ST?

RCC->AHB1ENR |= RCC_AHB1ENR_IOPDEN;

Человеку в любом случае придется полезть в документацию, чтобы узнать, что это за регистр AHB1ENR
и что за флаг RCC_AHB1ENR_IOPDEN, так зачем тогда намеренно портить код?

ezh213b 24 марта 2014 в 14:52 –1

1) Не согласен с вашей категоричностью.


Я считаю, что на первых порах важно помнить про то, что мы работаем со спецрегистрами.
Магические числа, лично мне, в этом помогают.
До знакомства с библиотеками я делал немного иначе:

#define RCCAHB1_ENR (*(unsigned long*)0x40023830)


#define RCCAHB1_PORTD 0x8
//Enable port D clocking
RCCAHB1_ENR |= RCCAHB1_PORTD;

Как вы, наверное, знаете в большинстве начальных программ для МК быстро конфигурируется
периферия, и дальше уже используются дефайны для работы с определенными регистрами (даже без
использования библиотек). И этих регистров не так много. Так вот, используя определения, люди
часто забывают о том, что на самом деле происходит с их коде. На курсах программирования МК
часто встречались случаи, когда студенты делали записи:

#define EnableLED1() ((unsigned long*)(0x40020C14) |= 0x2000)

А по прошествии недели спрашивали: «Какой хэддер нужно подключить, чтобы заработала функция
EnableLED1?».
Выносить что-либо в макросы стоит только когда вы на 100% уверены, что знаете как это работает.
Все выше сказанное только мое мнение, а не руководство к действию. Каждый сам решает как ему
оформлять код. По этому поводу написано миллионы страниц текста.

2) По поводу:

RCC->AHB1ENR |= RCC_AHB1ENR_IOPDEN

Есть несколько соображений:


1. Нужно немного разобраться с библиотекой, покопаться в примерах и т.д. Если пытаться все делать
сразу — будет каша в голове (ИМХО). Я предпочитаю начать с документов.
2. Я стараюсь описать принцип программирования МК на примре STM32, а не описать библиотеку и
возможности STM32. У TI, например, библиотека устроена совершенно иначе.
3 Ваш пример не показателен. Часто встречается ошибка — путаница с GPIO_PinSource8 и GPIO_Pin_8.
4. Не стимулирует. Вот пример который компилируется но не cработает:

RCC->AHB1ENR |= RCC_AHB1Periph_SRAM1;

После 3 инициализаций периферии люди автоматически начинают экстраполировать методы


инициализации. Это не верно. Главный посыл такой: сначала документация — затем
программирование.

grossws 24 марта 2014 в 13:38 0

Регистры у F4xx и F1xx отличаются, у тех же GPIO, допустим. Настраиваются по-разному, у F1xx режим с
настройками упиханы в общий регистр, у F4xx — отдельные регистры.

Ещё ощутимо различается настройка AF in, т. к. на F1xx используется IN_FLOATING, а на F4xx AF. Плюс
ремаппер, да.

farcaller 24 марта 2014 в 12:22 0

В библиотеках от производителя иногда встречаются ошибки

Я как-то час убил пытаясь сопоставить логику работы кода по инициализации UART в примере на TI'шный
stellaris launchpad и в мануале на него же. В итоге оказалось что алгоритм в примере был не правильный, но в
частном случае «по умолчанию» он таки работал.

Впрочем, бывают варианты с опечатками в документации, или даже с принципиально неправильно


работающей перифирией, так что когда не работает что-то, что в принципе должно, есть хороший повод
заглянуть в errata по МК (так я узнал что у lpc810 надо делать дополнительные танцы с бубном для прошивки
флеша прямо из МК).

ezh213b 24 марта 2014 в 13:18 0

Пример был от TI или шел со средой разработки?


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

farcaller 24 марта 2014 в 13:20 0

От TI, если мне не изменяет память. Я никогда не заморачивался Keil'ами и IAR'ами, потому что «vim'а
хватит везде» :-)

ezh213b 24 марта 2014 в 13:23 0

А как же Trace или банальный просмотр спецрегистров с описанием битовых полей?


Очень помогает в работе.

farcaller 24 марта 2014 в 13:34 0

У меня, наверно, было тяжелое детство, приходилось дебажить раннюю загрузку linux миганиями
светодиода, так что я вполне могу пережить без этих вот новомодных отладчиков (а по сути — я
никогда профессионально разработкой ПО в windows не занимался, потому нет привычки ко
всяким армовым иде).

Вообще есть openocd и gdb, их всегда хватает для отладки. Gdb вообще очень функционален и
удобен, если к нему попривыкнуть. А компилировать проект из Makefile — это вообще на порядок
очевиднее чем IDE должно быть, потому что видно, что происходит.

Zubaru 24 марта 2014 в 13:42 0

Скажите гуру, как лучше писать для контроллеров:


volatile int i = 0;
или
int volatile i = 0;
?
Спасибо

ezh213b 24 марта 2014 в 15:05 0

Гуру я себя не называл…


В данном случае разницы нет.
Хитрости с указателями. Можно почитать тут.
publib.boulder.ibm.com/infocenter/comphelp/v101v121/index.jsp?
topic=/com.ibm.xlcpp101.aix.doc/language_ref/cv_qualifiers.html

В первом примере как-то не понадобилось. Но вот когда дойдем до считывания данных из регистров —
вот тогда затронем этот вопрос.

alecv 24 марта 2014 в 14:52 0

Segger J-link PRO стоит 998$, как то не очень годится для DIY творчества
shop-us.segger.com/J_Link_PRO_p/8.12.00.htm

Уж мы как нибудь на Discovery…

ezh213b 24 марта 2014 в 14:57 0

Поэтому все примеры для Discovery.


Да и клонов от дружелюбных нам соседей предостаточно.

ezh213b 24 марта 2014 в 15:08 0

Стоит только открыть eBay:


www.ebay.com/sch/i.html?_from=R40&_trksid=m570.l2736&_nkw=j-link

alecv 25 марта 2014 в 16:55 0

Хехе. У меня брат один такой уже умер от прошивки 4.80. Как говорится, работает — не трогай.

ezh213b 25 марта 2014 в 17:11 0

Вот так не лечится?


cn0086.info/%E5%B7%A5%E5%85%B7%E5%88%B6%E4%BD%9C/repaire-j-link

alecv 28 марта 2014 в 17:02 0

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

Segger J-Link очень хорошо подходит к дешовым платам типа LC Technology по 7.99$
www.lctech-inc.com/Hardware/Default.aspx?Id=6e665f5e-20d7-4986-94da-bd5a89058f72
www.aliexpress.com/store/group/Cortex-Study-Board/524881_251395501.html

Но мне кажется, что Discovery все же круче и периферии в ней нашпиговано


больше.

googol 24 марта 2014 в 17:43 0

Недавно купил stm32f4 discovery — решил поиграться. Цель написать приложения


— без использования IDE, только make и command-line tools для прошивки (st-util)
— без использования библиотек, будь то стандартная от stm или libopencm3
— сделать приложение с минимум ассемблера, но при этом эффективно.

Все дело должно работать на Linux Arch (пришлось несколько пакетов переместить из AUR в [community]).

В общем после курения мануалов и интернетов получилась вот такое вот минималистическое приложение
(конечно же диодиком моргать) github.com/anatol/stm32f4-examples/tree/master/blink01

grossws 24 марта 2014 в 20:39 +3

Какой смысл не использовать CMSIS (для STM32 проще взять и выдрать её из SPL), которая
минималистична и даёт готовые define для адресов периферии? Проверить, что это возможно?

Удобство падает, вероятность сделать ошибку растет, больше внимания и усилий тратится не на логику и
control flow программы (а при наличии мало-мальски нетривиального набора прерываний он уже
довольно сложен), а на аккуратное вбивание адресов и смещений из datasheet'а.

Опять же это не спасет от, например, неправильной задержки после инициализации RCC (которая в
разных версиях для разных шин — разная). Или от попадания на какой-нибудь баг из errata.

пришлось несколько пакетов переместить из AUR в [community]

Вы Anatol Pomozov?

googol 24 марта 2014 в 21:10 0

Проверить, что это возможно?

Да! Выучить вещи такие инициализация устройства, инициализация приложения (bss/data секции); что
есть вектор прерываний, как работает линковка…

Если действительно хотите выучить программирование но нахер выбросить IDE/библиотеки и взять


написать простое приложение с минимумом зависимостей и пониманием каждой строчки что
написана. И только после этого брать и работать с библиотеками.

ploop 24 марта 2014 в 21:17 0

Постойте, но текст вроде не для тех, кто хочет научиться программировать? Он для тех, кто хочет
пощупать новую архитектуру. Думаю, читатель уже должен представлять, что такое библиотека и как
с ней работать, а уж заглянуть в файл с дефайнами и подавно.

grossws 24 марта 2014 в 21:38 0

Да, в рамках обучения это полезно. У вас, например, нет важных хэндлеров в таблице векторов
прерываний (NMI, HardFault, MemManage, BusFault, UsageFault, SVC, DebugMon, PendSV). В процессе
отладки stlink2 умеет сваливаться в HardFault, а у вас undefined behavior, т. к. секция .text будет лежать
в той части, где должна быть таблица векторов прерываний. Пока исполнительных устройств нет,
может и ничего, но при их наличии — довольно неприятно.

northbear 24 марта 2014 в 21:48 0

Обучение это прежде всего формирование понимания и правильных навыков. Потом, в процессе
реальной работы, когда важна скорость выдачи продукта, можно обвешиваться какими угодно
IDE и бирюльками. Но на данном этапе, я считаю, только так…

grossws 24 марта 2014 в 21:59 0

При чём здесь IDE? В нормальной IDE вы можете использовать make для сборки, написать
руками скрипт для линковщика и т. п.

Использование визардов IDE на начальном этапе (без понимания того, что они генерируют) —
зло.

Показываемое в статье повсеместное использование магических чисел не является


правильным навыком. Аргумент автора про EnableLED1 считаю несостоятельным. Если студент
так усваивает информацию, то он также будет тупо копировать адреса из одного исходника в
другой без какого-либо понимания.

Я вообще считаю, что обучаться Си лучше на простом контроллере, например, AVR. Ассемблер
там простой, спецрегистров немного, периферии — тоже.

northbear 24 марта 2014 в 22:25 0

Э-э… Вы отвечаете googol, но он не автор статьи.

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


языка. Там цена ошибки меньше. Запороть контроллер случайной опечаткой в коде как два
пальца об асфальт…
Что до магических чисел. Я, например, как адепт старой школы, на автопилоте всем
константам прописываю мнемонические названия.
В данном случае надо всё-таки определиться, учим правильным навыкам или всё-таки
программированию микроконтроллеров. В каждом случае фокусироваться надо на своём.

grossws 24 марта 2014 в 23:32 0

Крайний раз я отвечал вам, т. к. вы ответили на мой комментарий, адресованный


@ googol. Хотя в моём комментарии не было ничего про IDE.

Что до магических чисел. Я, например, как адепт старой школы, на автопилоте всем
константам прописываю мнемонические названия.

Это не старая школа, это — обычный, но необходимый, навык. Присутствие магических


чисел в коде — очень серьезный code smell.

Убить контроллер опечаткой — довольно сложно. Обычно на критичных регистрах есть


защита (например, перед изменением записать пару магических констант в спецрегистр,
и изменять в течении не более чем N тактов).

Я приверженец мнения, что Си можно изучить только работая, как минимум с памятью,
на низком уровне. Будет это на x86, x51, AVR, PIC, ARM — не суть важно. На x86, разве что,
порог вхождения ниже за счёт легкой работы с libc.

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

northbear 25 марта 2014 в 00:37 0

IDE я, конечно, для красного словца приплёл…

Прямо убить контроллер — это, конечно, надо постараться. А вот из-за набитой с
опечаткой чужой программки, как-то пришлось полдня потом промудохаться, чтобы
привести контроллер в чувство. Было дело… Он был уже запаян на плату…

farcaller 24 марта 2014 в 22:19 0

Сравнивая с habrahabr.ru/post/189484/ бирюльки в виде IAR появляются уже у вас :-) Впрочем я
нисколько не дискредитирую вашу статью — тема разбора reference manual у вас раскрыта
лучше.

northbear 24 марта 2014 в 22:30 0

Пардон, но лично я давно и счастливо сижу на Linux'e, и IAR стараюсь обходить стороной.
Лично моё отношение к подобным IDE сугубо отрицательное. Правда, я не занимаюсь
микроконроллерами профессионально. Для меня это хобби…

И статья увы не моя… ))

farcaller 24 марта 2014 в 22:38 0

Извиняюсь, что-то у меня было минутное помутнение :-)

Кстати говоря, коллеги, которые занимаются эмбеддедами профессионально,


комментируют все вот эти «сборка вручную» и «мейкфайлы» как игры в песочнице :-)

northbear 25 марта 2014 в 00:27 +2

Да. знаю таких товарищей. Они обычно от Linux'а шарахаются как от огня. Этим и
объясняется их «снисходительность» к «сборкам вручную» и «мейкфайлам»…

Каких либо серьезных аргументов за IAR' ы и Keil'ы я от них так и не услышал. Обычно
приводят аргумент с интеграцией с инструментами сквозного проектирования, типа
Altium и иже сними… Когда например, port mappings может генерироваться в
зависимости от разводки платы. И еще пара специфических фишек, которые на деле
ими мало используются…

Зато, послушаешь рассказы как глючат эти IAR'ы порой, это вообще пипец… Когда про
цену на Keil/IAR'ы говорить начинаешь они тоже грустнеют… В общем всё говорит за
то, что это больше вопрос привычки а не каких-то реальных профитов…

grossws 25 марта 2014 в 01:04 0

Моя информация, вероятно, устарела, но, вроде, в Keil uVision (могу путать с IAR, т. к.
в то время работал в одном, делал хоббийные проекты в другом) для x51 в 2006,
примерно, году не было:
— нормальной отладки (да-да, embedded среда),
— почти не было подсветки синтаксиса,
— навигации по коду (например, перейти к объявлению/реализации, перейти в
заголовочный файл),
— автодополнения (имен файлов, названий функций, полей в структурах).

Про всякие фичи типа элементарного рефакторинга и говорить нечего.

northbear 24 марта 2014 в 21:41 +3

Ага, так вот кому надо сказать за перемещение arm-none-eabi-gcc и иже с ним в community repo…
Спасибо… )

grossws 24 марта 2014 в 21:42 0

Присоединяюсь =)

googol 24 марта 2014 в 21:48 +2

:D Завсегда пожалуйста, пользуйтесь на здоровье.

ProLimit 24 марта 2014 в 21:49 0

Я бы не рекомендовал напрямую работать с регистрами, особенно для новичков. У STM есть неплохая
прослойка в виде библиотек, и читаемость кода выше, и портирование кода на другие версии процессора
будет проще. Конечно полезно знать, что делает библиотечная функция, но совсем не обязательно это
помнить — всегда можно посмотреть, открыв ее код или define

jonic 24 марта 2014 в 23:08 0

Ох, помню как я втыкал в архитектуру F1, а потом заказал себе F4, и долго пытался понять разницу работы и
победить компилятор :)). Потом прозрел и читаю документацию можно сказать прямо в заголовках. Пишу на
кокосе. Уже объяснил паре товарищей как работать с кокосом и программированием под
микроконтроллеры. :) Один радиолюбитель был, какие же у него были азартные глаза когда он понял что он
делает и что может сделать :)
А обучение мое довольно жестокое, грызи, грызи, грызи. Вот это глянь и пойми почему так, а не этак. Так что
я за либы от stm как никак а переделки малы, и можно даже окружиться в своих либах дефайнами по
платформе что бы компилилось и под f1 и под f4. Конечно не универсальное решение, но вполне сгодится
для общих задач. ЗА статью определенно +, все таки описывает многие аспекты программирования для
начинающих.

alexfl 4 апреля 2015 в 00:20 0

«Как видно из описания для совершения требуемой нам настройки необходимо записать значение 01b в 26-
27 биты регистра GPIOx_MODER. Адрес регистра можно определить тем же методом, что описан выше.»
Помогите вкурить, как число можно в биты впихнуть?

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

САМОЕ ЧИТАЕМОЕ

Сутки Неделя Месяц

Судьба предателя, угнавшего новейший МиГ-25 в Японию


+42 130k 63 718

Обратная сторона Open Source-славы: как угрожают автору curl


+84 34,1k 36 86

Черные дыры могут иметь «волосы». Эйнштейн не прав?


+15 33,3k 23 16

На 30 тысячах компьютеров с macOS нашли странный зловред, который ждёт команду


+16 14,7k 23 29

Куда же плывут облака? Подбиваем итоги опроса и даём аналитику


Мегапост

Ваш аккаунт Разделы Информация Услуги

Войти Публикации Устройство сайта Реклама

Регистрация Новости Для авторов Тарифы

Хабы Для компаний Контент

Компании Документы Семинары

Пользователи Соглашение Мегапроекты

Песочница Конфиденциальность Мерч

© 2006 – 2021 «Habr» Настройка языка О сайте Служба поддержки Мобильная версия

Вам также может понравиться