Программирование stm32 с чего начать. STM32 с нуля. Введение

Для Windows Phone 06.06.2019
Для Windows Phone

Данная статья является первой в планируемом цикле статей по изучению программирования микроконтроллеров. Изучая различные материалы я отметил, что практически все они начинаются с того, что новичку предлагается скачать (или использовать идущую со средой разработки) библиотеку для работы с периферийными устройствами и использовать ее для написания своей первой программы (обычно мигание светодиодом).

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

В этой статье, путь от фразы «Да, я хочу попробовать!» до радостного подмигивания светодиода, будет значительно длиннее чем у других авторов. Я постараюсь раскрыть аспекты программирования микроконтроллеров, которые прячутся за использованием библиотечных функций и готовых примеров.
Если вы намерены серьезно изучать программирование микроконтроллеров данная статья для вас. Возможно, она может заинтересовать и тех, кто вдоволь наигрался с Arduino и хочет получить в свои руки все аппаратные возможности железа.

Выбор микроконтроллера

Многие могут сказать, что начинать изучение микроконтроллеров лучше с 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 есть возможность получить временную лицензию без ограничения функционала, либо безлимитную лицензию с ограничением по размеру кода (32КБ для МК это очень много).
Помимо этого, сразу замечу, что для некоторых МК не существует бесплатных сред разработки. И к сожалению эти МК в некоторых областях незаменимы.


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

С чего начать?

Создание проекта
Для начала создадим пустой проект. 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

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

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

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

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

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

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

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

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

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

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

Пример

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

Взаимодействие ядра с периферийным блоком
Взаимодействие ядра МК с периферийным блоком осуществляется с помощью спецрегистров (есть еще взаимодействие через механизм прерываний и 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 написан не для одного МК, а для нескольких линеек. Это говорит о том, что код будет переносим при переходе на другие МК в этих линейках (если конечно не пытаться использовать периферийные блоки которых нет в используемом МК).

В первую очередь необходимо определиться с какими блоками предстоит работать. Для это достаточно изучит разделы 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:

Для получения адреса регистра, необходимо к начальному значению адресного пространства блока 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)) | (0x04000000); //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)) | (0x04000000); 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)) | (0x04000000); 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 тактов. Это непозволительная роскошь для некоторых задач.

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

ST-LINK/V2 позволяет программировать и отлаживать контроллеры STM32 и STM8, и поддерживается такими популярными средами разработки как Keil uVision, IAR EWARM, CoIDE, IAR EWSTM8 и другими. Оригинальный программатор стоит около 50$ (в чайне видел такой же за 35$). Также у этого программатора куча китайских клонов, различающихся степенью урезаности (отсутствием компонентов), вплоть до свистков за 7$. Кроме того, программатор распаян на платах STM*Discovery, опять же в урезанной версии и умеющий работать только с STM32 или только с STM8 в зависимости от того, какая дискавери.

Но иногда проще и интереснее сделать программатор самому, особенно если для этого не нужно много усилий. Все детали мне обошлись меньше чем в 5$ в местном магазине радиодеталей. Прошить сам программатор можно по интерфейсу UART (например с помощью любого преобразователя USB- UART).

Что умеет программатор/отладчик ST-LINK-V2:

  • Прошивка и отладка STM32 по интерфейсу SWD (пины SWCLK и SWDIO) + RST (не обязательно)
  • Необязательный пин SWO для последовательного терминала с внешним STM32 без дополнительных UART-ов и т.д. (Можно выполнять printf/scanf на STM32 с помощью программатора)
  • Прошивка и отладка STM8 по интерфейсу SWIM
  • Выдача внешнего питания 3.3В от программатора
  • Подключение по mini-usb

Для контроллеров STM32 и STM8 у меня уже есть программатор Versaloon , но его недостаток в том, что он не умеет отлаживать STM8 (только прошивает) и не поддерживается популярными средами разработки. Вообще по конструкции ST-LINK/V2 очень похож на versaloon. В программаторе используется тот же микроконтроллер STM32F103C8, ну и плюс минимальная обвязка. Основу схемы я взял из статьи Highlander-а в сообществе easyelectronics. Highlander поделился всей информацией, которая нужна для сборки программатора, за что ему огромное спасибо.

Поддерживаемые чипы

Программатор может использоваться для прошивки микросхем STM32:

  • STM32F100xx, STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx, STM32F107xx, STM32F2xxx, STM32F4xxx,
  • STM32L15xx6, STM32L15xx8,STM32L15xxB, STM32L151xC, STM32L151xD, STM32L152xC, STM32L152xD, STM32L162xD,
  • STM32TS60,
  • STM32W108C8, STM32W108xB, STM32W108xC, STM32W108xZ
  • STM8AF51x, STM8AF52x, STM8AF61x, STM8AF62x,STM8AH51x, STM8AH61x,
  • STM8S003K3, STM8S003F3, STM8S005C6, STM8S005K6, STM8S007C8, STM8S103xx, STM8S105xx, STM8S207xx, STM8S208xx, STM8S903F3, STM8S903K3,
  • STM8L101xx, STM8L15x, STM8L16x,
  • STM8TL52x4,STM8TL53x4

Проект

Схему я перерисовал в eagle, резисторы и конденсаторы у меня в корпусе 0805 (которые лежали в магазине). Также я заменил сборки стабилитронов на одиночные стабилитроны, которые удалось купить. Кроме того по причине ненадобности я убрал возможность переключения выходного напряжения между 5В и 3.3В, оставил только 3.3В. Из интерфейсов программирования STM32 я оставил только SWD, убрав громоздкий JTAG(ни разу его не использовал для Cortex-M3 и не думаю что когда- нибудь буду).

В результате схема выглядит так:

Необходимые компоненты:

Количество Описание Номинал Корпус Комопненты
9 Конденсатор 0.1u 0805 C1-C3, C5, C8-C10, C12,C13
1 Резистор 1.5k 0805 R3
1 Резистор 10 0805 R12
2 Резистор 100k 0805 R6, R11
2 Конденсатор электролит, тантал 10u EIA3528, CASE B C4, C11
2 2-х пиновые штырьки 0.1" 1x2 1X02 JP1, JP2
2 Конденсатор 20p 0805 C6, C7
8 Резистор 22 0805 R4, R5, R13-R18
3 Резистор 220 0805 R7-R9
1 Регулятор напряжения LM1117 3V3 Fixed SOT223 IC2
2 Резистор 4.7k 0805 R1, R2
2 Резистор 510 0805 R19, R20
7 SMD-Стабилитрон 5V1 SOD80C VD1-VD7
1 Резистор 680 0805 R10
1 Стандартный кварц HC49UV 8 MHz HC49U-V Y1
1 Разъем BH-10 или штырьки 5x2 BH-10 2X5 JP3
1 Индуктивность BLM18AG121SN1D 0805 L1
1 Светодиод Green 3 мм LED1
1 Светодиод Red 3 мм LED2
1 ST STM32F101/103 48pin LQFP-48 STM32F103C8T6 LQFP-48 IC1
1 Mini-USB "B" коннектор 5 пинов USB-MINIB-5PIN USB-MINIB JP4

Вместо индуктивности можно на крайний случай просто запаять перемычку, или резистор в 0 Ом. Регулятор напряжения должен быть любой в корпусе SOT223 на 3.3 В фиксированный, например у меня LD1117-3.3. Вместо STM32F103C8T6(64K flash) можно также взять более дорогой STM32F103CBT6 (128K flash). Электролиты конденсаторы можно брать в диапазон 4.7-47мкФ.

В принципе из схемы можно также убрать стабилитроны и поменять корпус компонентов на 0603 и в результате уменьшить форм-фактор до маленького свистка на подобии Versaloon Nano. Ну а я ограничился платой, вставляемой в дешевый пластиковый корпус 57х38х19:

Карта запайки компонентов:

Распиновка разъема:

Разъем может быть обжат под цветной шлейф, так, чтобы цвета совпадали с теми что на картинке. Тогда концы шлейфа можно зацепить на контакты BLS и подключать их по картинке. Однако удобнее сделать шлейф- переходник на 4-х пиновое SWIM-гнездо HU-4 (под штекер WH-4 на плату с целевым контроллером STM8) и HU-5 для SWD.

Конструирование

Плата хоть и двухсторонняя, но достаточно простая в изготовлении. Две, лазерно распечатанные стороны платы на листе фотобумаги (я использую фотобумагу плотностью 120г/м2) сопоставляются на свету и скрепляются степлером. Затем обезжиренный кусочек двухстороннего текстолита аккуратно вставляется в этот конверт, и утюжится при достаточной температуре (чтобы тонер не поплыл и пленка фотобумаги не запекалась важно не перегреть):


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


После травления платы можно начинать сверлить:

  • Для переходных я использовал сверла 0.5мм;
  • для 4х отверстий под светодиоды - 0.7мм;
  • отверстия под разъем BH-10, двух штырьков джампера, двух крепежных отверстий под USB - 1мм;
  • для двух боковых крепежных отверстий - 3 мм

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

После сверления, при помощи обычной наждачной шкурки были сформированы контуры платы:


Лужение платы я выполнил паяльником, смазав плату обычным глицерином. Для запайки переходных хорошо подошла медная жила, изъятая из телефонного провода, обзываемого в народе "лапшой". После запайки можно слегка пройтись шкуркой по заостренным отроскам.


Если вы хотите сделать корпус для устройства, то самое время этим заняться. Для начала прикладываем плату, как указанно ниже на фото и сверлим поверх существующих обведенные отверстия. Затем переворачиваем эту половинку корпуса и вставляем в нее разъемы.


Вставленные разъемы нужно обвести канцелярским ножом или чем-то подобным. После обведения можно убрать разъемы и прорезать отверстия. Для светодиодов можно просто просверлить 3-х миллиметровые отверстия. Также для крепежных болтов М3 нужно подобрать или изготовить стойки(я отрезал по кусочку толстой изоляции)



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


Если все подходит, можно запаивать верхний слой и помещать все окончательно в корпус:

Также можно скотчем приклеить небольшую наклейку с подписью пинов разъёма (лучше цветную):

Прошивка программатора

Для прошивки нужен UART интерфейс с компьютером с уровнями TTL. Я использовал вот такой самодельный преобразователь USB-UART. Если же у вас в компьютере есть RS232, то можно обойтись несложным переходником в TTL на каком-нибудь MAX232. А вообще всяких USB-UARTO-в сейчас в продаже очень много и цена на уже готовые преобразователи не превышает 2$.

Прошивка может быть выполнена из Windows с помощью утилиты Flash Loader Demonstrator (она есть в архиве). Пользователям Linux придется запустить виртуальную систему в Virtualbox (в виртуальную систему можно пробросить как преобразователи USB-UART, так и RS232 порт).

Перед началом прошивки прогрмматора возможно понадобится выпаять резистор R20 (который соединяет красный светодиод и линию U1_BL_TX). Мне выполнить прошивку без этого действия не удалось. Хотя подозреваю что это зависит от UART-а.

Итак последовательность действий по прошивке:

1. Установить на джампер Boot

2. Подключить землю, RX, TX UART-a. RX преобразователя нужно подключить к TX на разъеме программатора, а TX преобразователя соответственно к RX программатора. Если есть откуда, подключите питание к пину 3.3В на разъеме программатора (тогда при прошивке программатора можно будет не подключать его к USB). Подключить UART к компьютеру. Если питание к пину 3.3В не подключено, то нужно также подключить программатор к компьютеру, что бы запитать его. Посмотреть какой COMх порт занял ваш UART преобразователь.

3. Запустить Flash Loader Demonstrator. Выбрать COMx порт, остальные настройки оставить как есть:

4. Нажать Next. Если UART подключен правильно, питание подано, то должно появится такое окно:

Если появляется ошибка, то проверьте физический контакт. Опять повторюсь что на этом этапе может помешать светодиод, соединяющей линию TX с землей, по этому его стоит временно отключить выпаяв R20.

5. Нажимаем Next два раза. В следующем окне указываем путь к прошивке firmwareSTLinkV2.J16.S4.bin:

6. Опять нажимаем Next и ожидаем завершения прошивки. После успешного завершения программирования окно должно приобрести такой вид:

7. Отключаем преобразователь и питание с программатора.

8. Теперь самое главное: нужно обновить прошивку, иначе программатор не заработает. Для начала установим драйвер windriverst- link_v2_usbdriver.exe.

9. Подключаем программатор. Когда запустится мастер установки нового оборудования, выбираем «автоматический поиск драйверов».

10. Запускаем upgradeST-LinkUpgrade.exe(папка upgrade обязательно должна быть распакована). В открывшемся окне прожимаем Device Connect, Yes. Должно появится окно Upgrade is successful.

Недавно коллега меня подсадил на идею создания умного дома, я даже успел заказать себе десятки разных датчиков. Встал вопрос о выборе Микроконтроллера (далее МК) или платы. После некоторых поисков нашёл несколько вариантов. Среди них были и Arduino (включая его клоны, один из которых себе заказал ради того, чтобы просто побаловаться) и Launchpad , но всё это избыточно и громоздко (хотя в плане программирования гораздо проще, но тему холиваров поднимать не буду, у каждого свои вкусы). В итоге решил определяться не с готовой платой, а взять только МК и делать всё с нуля. В итоге выбирал между Atmel ATtiny (2313), Atmel ATmega (решил отказаться т.к. не смог найти за адекватные деньги), STM32 (Cortex на ядре ARM ). С тинькой я уже успел побаловаться, так что взял себе STM32VL-Discovery . Это можно назвать вступлением к циклу статей по STM32 . Оговорюсь сразу, автором большинства этих статей буду являться не я, т.к. сам только познаю, здесь я публикую их в первую очередь для себя, чтоб удобнее было искать если что-то забуду. И так поехали!

Общие сведения

Микроконтроллеры семейства STM32 содержат в своём составе до семи 16-разрядных портов ввода-вывода c именами от PORTA до PORTG. В конкретной модели микроконтроллера без исключений доступны все выводы портов, общее количество которых зависит от типа корпуса и оговорено в DataSheet на соответствующее подсемейство.

Для включения в работу порта x необходимо предварительно подключить его к шине APB2 установкой соответствующего бита IOPxEN в регистре разрешения тактирования периферийных блоков RCC_APB2ENR :

RCC->APB2ENR |= RCC_APB2ENR_IOPxEN; // Разрешить тактирование PORTx.

Управление портами STM32 осуществляется при помощи наборов из семи 32-разрядных регистров:

  • GPIOx_CRL, GPIOx_CRH – задают режимы работы каждого из битов порта в качестве входа или выхода, определяют конфигурацию входных и выходных каскадов.
  • GPIOx_IDR – входной регистр данных для чтения физического состояния выводов порта x.
  • GPIOx_ODR – выходной регистр осуществляет запись данных непосредственно в порт.
  • GPIOx_BSRR – регистр атомарного сброса и установки битов порта.
  • GPIOx_BSR – регистр сброса битов порта.
  • GPIOx_LCKR – регистр блокировки конфигурации выводов.

Режимы работы выводов GPIO

Режимы работы отдельных выводов определяются комбинацией битов MODEy и CNFy регистров GPIOx_CRL и GPIOx_CRH (здесь и далее: x-имя порта, y- номер бита порта).

GPIOx_CRL - регистр конфигурации выводов 0...7 порта x :

Структура регистра GPIOx_CRH аналогична структуре GPIOx_CRL и предназначена для управления режимами работы старших выводов порта (биты 8...15).

Биты MODEy указанных регистров определяют направление вывода и ограничение скорости переключения в режиме выхода:

  • MODEy = 00: Режим входа (состояние после сброса);
  • MODEy = 01: Режим выхода, максимальная скорость – 10МГц;
  • MODEy = 10: Режим выхода, максимальная скорость – 2МГц;
  • MODEy = 11: Режим выхода, максимальная скорость – 50МГц.

Биты CNF задают конфигурацию выходных каскадов соответствующих выводов:

в режиме входа:

  • CNFy = 00: Аналоговый вход;
  • CNFy = 01: Вход в третьем состоянии (состояние после сброса);
  • CNFy = 10: Вход с притягивающим резистором pull-up (если PxODR=1) или pull-down (если PxODR=0);
  • CNFy = 11: Зарезервировано.

в режиме выхода:

  • CNFy = 00: Двухтактный выход общего назначения;
  • CNFy = 01: Выход с открытым стоком общего назначения;
  • CNFy = 10: Двухтактный выход с альтернативной функцией;
  • CNFy = 11: Выход с открытым стоком с альтернативной функцией.

С целью повышения помехоустойчивости все входные буферы содержат в своём составе триггеры Шмидта. Часть выводов STM32 , снабженных защитными диодами, соединёнными с общей шиной и шиной питания, помечены в datasheet как FT (5V tolerant) - совместимые с напряжением 5 вольт.

Защита битов конфигурации GPIO

Для защиты битов в регистрах конфигурации от несанкционированной записи в STM32 предусмотрен регистр блокировки настроек GPIOx_LCKR
GPIOx_LCKR - регистр блокировки настроек вывода порта:

Для защиты настроек отдельного вывода порта необходимо установить соответствующий бит LCKy. После чего осуществить последовательную запись в разряд LCKK значений "1” - "0” - "1” и две операции чтения регистра LCKR , которые в случае успешной блокировки дадут для бита LCKK значения "0” и "1” . Защита настроечных битов сохранит своё действие до очередной перезагрузки микроконтроллера.

Файл определений для периферии микроконтроллеров STM32 stm32f10x.h определяет отдельные группы регистров, объединённые общим функциональным назначением (в том числе и GPIO ), как структуры языка Си, а сами регистры как элементы данной структуры. Например:

GPIOC->BSRR – регистр BSRR установки/сброса порта GPIOC.
Воспользуемся определениями из файла stm32f10x.h для иллюстрации работы с регистрами ввода-вывода микроконтроллера STM32F100RB установленного в стартовом наборе STM32VLDISCOVERY :

#include "stm32F10x.h" u32 tmp; int main (void) { RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // Разрешить тактирование PORTC. GPIOC->CRH |= GPIO_CRH_MODE8; // Вывод светодиода LED4 PC8 на выход. GPIOC->CRH &=~GPIO_CRH_CNF8; // Двухтактный выход на PC8. GPIOC->CRH |= GPIO_CRH_MODE9; // Вывод светодиода LED3 PC9 на выход. GPIOC->CRH &=~GPIO_CRH_CNF9; // Двухтактный выход на PC9. GPIOA->CRL&=~GPIO_CRL_MODE0; // Кнопка "USER" PA0 - на вход. // Заблокировать настройки выводов PC8, PC9. GPIOC->LCKR = GPIO_LCKR_LCK8|GPIO_LCKR_LCK9| GPIO_LCKR_LCKK; GPIOC->LCKR = GPIO_LCKR_LCK8|GPIO_LCKR_LCK9; GPIOC->LCKR = GPIO_LCKR_LCK8|GPIO_LCKR_LCK9| GPIO_LCKR_LCKK; tmp=GPIOC->LCKR; tmp=GPIOC->LCKR; }

Запись и чтение GPIO

Для записи и чтения портов предназначены входной GPIOx_IDR и выходной GPIOx_ODR регистры данных.

Запись в выходной регистр ODR порта настроенного на вывод осуществляет установку выходных уровней всех разрядов порта в соответствии с записываемым значением. Если вывод настроен как вход с подтягивающими резисторами, состояние соответствующего бита регистра ODR активирует подтяжку вывода к шине питания (pull-up, ODR=1) или общей шине микроконтроллера (pull-down, ODR=0).

Чтение регистра IDR возвращает значение состояния выводов микроконтроллера настроенных как входы:

// Если кнопка нажата (PA0=1), установить биты порта C, иначе сбросить. if (GPIOA->IDR & GPIO_IDR_IDR0) GPIOC->ODR=0xFFFF; else GPIOC->ODR=0x0000;

Сброс и установка битов порта

Для атомарного сброса и установки битов GPIO в микроконтроллерах STM32 предназначен регистр GPIOx_BSRR . Традиционный для архитектуры ARM способ управления битами регистров не требующий применения операции типа "чтение-модификация-запись” позволяет устанавливать и сбрасывать биты порта простой записью единицы в биты установки BS (BitSet) и сброса BR (BitReset) регистра BSRR . При этом запись в регистр нулевых битов не оказывает влияния на состояние соответствующих выводов.

GPIOx_BSRR – регистр сброса и установки битов порта:

GPIOC->BSRR=GPIO_BSRR_BS8|GPIO_BSRR_BR9; // Зажечь LED4 (PC8), погасить LED3. GPIOC->BSRR=GPIO_BSRR_BS9|GPIO_BSRR_BR8; // Зажечь LED3 (PC9), погасить LED4.

Альтернативные функции GPIO и их переназначение (remapping)
Практически все внешние цепи специального назначения STM32 (включая выводы для подключения кварцевых резонаторов, JTAG/SWD и так далее) могут быть разрешены на соответствующих выводах микроконтроллера, либо отключены от них для возможности их использования в качестве выводов общего назначения. Выбор альтернативной функции вывода осуществляется при помощи регистров с префиксом "AFIO ”_.
Помимо этого регистры AFIO _ позволяют выбирать несколько вариантов расположения специальных функций на выводах микроконтроллера. Это в частности относится к выводам коммуникационных интерфейсов, таймеров (регистры AFIO_MAPR ), выводам внешних прерываний (регистры AFIO_EXTICR ) и т. д.

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

Итак, для начала разберемся, что же нужно знать и уметь, чтобы начать изучать ARM’ы. А, в принципе, ничего супер сложного и фееричного 😉 Конечно, на контроллеры ARM люди обычно переходят, уже наигравшись с PIC’ами и AVR’ками, то есть в большинстве своем опытные разработчики. Но я постараюсь максимально подробно и понятно описывать все то, что мы будем разбирать, чтобы те, кто впервые решил попробовать себя в программировании микроконтроллеров, могли легко разобраться в материале. Кстати, если будут возникать какие-нибудь вопросы, или просто что-то будет работать не так, как задумывалось, пишите в комментарии, постараюсь разобраться и помочь.

Теперь перейдем к техническим вопросам) Несколько раз я уже упомянул название «Учебный курс ARM», но, по большому счету, это не совсем верно. Микроконтроллера ARM как такового не существует. Есть контроллер с ядром(!) ARM, а это, согласитесь, все-таки не одно и то же. Так вот, такие девайсы выпускает ряд фирм, среди которых особо выделяются, STMicroelectronics и NXP Semiconductors. Соответственно выпускают они контроллеры STM и LPC. Я остановил свой выбор на STM32, они мне просто больше понравились =) У STM очень подкупает, что разобравшись с любым МК из линейки STM32F10x, не возникнет никаких проблем и с любым другим. Одна линейка – один даташит. Кстати есть огромное количество как дорогих, так и не очень, отладочных плат с контроллерами STM32, что очень радует, хотя первое время будем отлаживать наши программы в симуляторе, чтобы оценить возможности контроллера, прежде чем покупать железо. Вот, на всякий случай, официальный сайт STMicroelectronics – .

Как то плавно выехали на тему компилятора, так что скажу пару слов об этом. Я, недолго думая, выбрал Keil, не в последнюю очередь из-за мощного встроенного симулятора. Можно и на UART там посмотреть, и на любой регистр, и даже логический анализатор имеется в наличии. Словом, у меня Keil оставил в основном только приятные впечатления, хотя есть и минусы, конечно, но не катастрофические. Так что можете смело качать Keil uvision4 с офф. сайта (). Правда есть одно НО – IDE платная, но доступен демо-режим с ограничением кода в 32кБ, которых нам пока с лихвой хватит. Кому этого мало есть огромное количество кряков для Keil’а 😉 Устанавливается все без проблем – пару раз тыкаем далее и все отлично ставится и работает без дополнительных танцев с бубном.

Собственно, вот и все, что я хотел тут рассказать, пора переходить от слов к делу, но это уже в следующей статье. Будем изучать программирование микроконтроллеров STM32 с нуля!

Данная статья является первой в планируемом цикле статей по изучению программирования микроконтроллеров. Изучая различные материалы я отметил, что практически все они начинаются с того, что новичку предлагается скачать (или использовать идущую со средой разработки) библиотеку для работы с периферийными устройствами и использовать ее для написания своей первой программы (обычно мигание светодиодом).

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

В этой статье, путь от фразы «Да, я хочу попробовать!» до радостного подмигивания светодиода, будет значительно длиннее чем у других авторов. Я постараюсь раскрыть аспекты программирования микроконтроллеров, которые прячутся за использованием библиотечных функций и готовых примеров.
Если вы намерены серьезно изучать программирование микроконтроллеров данная статья для вас. Возможно, она может заинтересовать и тех, кто вдоволь наигрался с Arduino и хочет получить в свои руки все аппаратные возможности железа.

Выбор микроконтроллера

Многие могут сказать, что начинать изучение микроконтроллеров лучше с 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 есть возможность получить временную лицензию без ограничения функционала, либо безлимитную лицензию с ограничением по размеру кода (32КБ для МК это очень много).
Помимо этого, сразу замечу, что для некоторых МК не существует бесплатных сред разработки. И к сожалению эти МК в некоторых областях незаменимы.


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

С чего начать?

Создание проекта
Для начала создадим пустой проект. 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

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

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

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

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

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

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

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

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

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

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

Пример

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

Взаимодействие ядра с периферийным блоком
Взаимодействие ядра МК с периферийным блоком осуществляется с помощью спецрегистров (есть еще взаимодействие через механизм прерываний и 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 написан не для одного МК, а для нескольких линеек. Это говорит о том, что код будет переносим при переходе на другие МК в этих линейках (если конечно не пытаться использовать периферийные блоки которых нет в используемом МК).

В первую очередь необходимо определиться с какими блоками предстоит работать. Для это достаточно изучит разделы 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:

Для получения адреса регистра, необходимо к начальному значению адресного пространства блока 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)) | (0x04000000); //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)) | (0x04000000); 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)) | (0x04000000); 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 тактов. Это непозволительная роскошь для некоторых задач.

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



Рекомендуем почитать

Наверх