Начинаем изучать Cortex-M на примере 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 позволяет отлаживать программы с использованием симулятора. Но я ни разу на практике не встречал его использования.


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

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

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

Не будем отходить от классики. Первым проектом будет мигающий светодиод. Благо на плате их предостаточно.Что же это означает с точки зрения программирования? Первым делом необходимо изучить принципиальную схему демонстрационной платы и понять как «заводится» светодиод.
User manualдоступен на сайте производителя. В данном описании даже есть отдельный раздел про светодиоды на плате -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 доступен на сайте производителя. Страниц много, но все читать пока не нужно. Как уже было сказано, большую часть документации составляет описание периферийных блоков и их регистров. Так же хочу обратить внимание на то, что этот 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)) | (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 тактов. Это непозволительная роскошь для некоторых задач.


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

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 39

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

    Поэтому я бы не стал говорить что не важно, что юзать — F1xx или F4xx, у начинающих запросто могут вылезти проблемы, оттого, что, скажем, они пишут код как для F1xx, пытаются его использовать на F4xx, но не включили мэпинг периферии.

    P.S. Магические числа в коде — это адЪ. Хоть бы константы именованные юзали.
      0
      Для этого я специально указал, что лучше использовать F2 или F4:
      Если у вас есть в наличии любая другая плата на базе STM32F2xx/STM32F4xx, вы сможете работать с ней.

      Вообще говоря, складывается впечатление, что на F1 ST «обкатывали» технологии и накапливали проблемы и баги. F2 сильно отличается от F1, а вот последующие уже очень похожи.
      К сожалению, невозможно было выбрать один универсальный МК. Также не вижу смысла писать обязательные требования по какой-то определенной отладочной плате.

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

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

      В дальнейшем, конечно же, в своих постах я уйду от прямого ковыряния в регистрах и буду пользоваться библиотекой.
        +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, так зачем тогда намеренно портить код?
          –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 инициализаций периферии люди автоматически начинают экстраполировать методы инициализации. Это не верно. Главный посыл такой: сначала документация — затем программирование.
        0
        Регистры у F4xx и F1xx отличаются, у тех же GPIO, допустим. Настраиваются по-разному, у F1xx режим с настройками упиханы в общий регистр, у F4xx — отдельные регистры.
        Ещё ощутимо различается настройка AF in, т. к. на F1xx используется IN_FLOATING, а на F4xx AF. Плюс ремаппер, да.
        0
        В библиотеках от производителя иногда встречаются ошибки
        Я как-то час убил пытаясь сопоставить логику работы кода по инициализации UART в примере на TI'шный stellaris launchpad и в мануале на него же. В итоге оказалось что алгоритм в примере был не правильный, но в частном случае «по умолчанию» он таки работал.

        Впрочем, бывают варианты с опечатками в документации, или даже с принципиально неправильно работающей перифирией, так что когда не работает что-то, что в принципе должно, есть хороший повод заглянуть в errata по МК (так я узнал что у lpc810 надо делать дополнительные танцы с бубном для прошивки флеша прямо из МК).
          0
          Пример был от TI или шел со средой разработки?
          Иногда примеры для среды разработки делают совершенно другие люди.
            0
            От TI, если мне не изменяет память. Я никогда не заморачивался Keil'ами и IAR'ами, потому что «vim'а хватит везде» :-)
              0
              А как же Trace или банальный просмотр спецрегистров с описанием битовых полей?
              Очень помогает в работе.
                0
                У меня, наверно, было тяжелое детство, приходилось дебажить раннюю загрузку linux миганиями светодиода, так что я вполне могу пережить без этих вот новомодных отладчиков (а по сути — я никогда профессионально разработкой ПО в windows не занимался, потому нет привычки ко всяким армовым иде).

                Вообще есть openocd и gdb, их всегда хватает для отладки. Gdb вообще очень функционален и удобен, если к нему попривыкнуть. А компилировать проект из Makefile — это вообще на порядок очевиднее чем IDE должно быть, потому что видно, что происходит.
          0
          Скажите гуру, как лучше писать для контроллеров:
          volatile int i = 0;
          или
          int volatile i = 0;
          ?
          Спасибо
          0
          Segger J-link PRO стоит 998$, как то не очень годится для DIY творчества
          shop-us.segger.com/J_Link_PRO_p/8.12.00.htm

          Уж мы как нибудь на Discovery…
          0
          Недавно купил stm32f4 discovery — решил поиграться. Цель написать приложения
          — без использования IDE, только make и command-line tools для прошивки (st-util)
          — без использования библиотек, будь то стандартная от stm или libopencm3
          — сделать приложение с минимум ассемблера, но при этом эффективно.

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

          В общем после курения мануалов и интернетов получилась вот такое вот минималистическое приложение (конечно же диодиком моргать) github.com/anatol/stm32f4-examples/tree/master/blink01
            +3
            Какой смысл не использовать CMSIS (для STM32 проще взять и выдрать её из SPL), которая минималистична и даёт готовые define для адресов периферии? Проверить, что это возможно?

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

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

            пришлось несколько пакетов переместить из AUR в [community]
            Вы Anatol Pomozov?
              0
              Проверить, что это возможно?


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

              Если действительно хотите выучить программирование но нахер выбросить IDE/библиотеки и взять написать простое приложение с минимумом зависимостей и пониманием каждой строчки что написана. И только после этого брать и работать с библиотеками.
                0
                Постойте, но текст вроде не для тех, кто хочет научиться программировать? Он для тех, кто хочет пощупать новую архитектуру. Думаю, читатель уже должен представлять, что такое библиотека и как с ней работать, а уж заглянуть в файл с дефайнами и подавно.
                  0
                  Да, в рамках обучения это полезно. У вас, например, нет важных хэндлеров в таблице векторов прерываний (NMI, HardFault, MemManage, BusFault, UsageFault, SVC, DebugMon, PendSV). В процессе отладки stlink2 умеет сваливаться в HardFault, а у вас undefined behavior, т. к. секция .text будет лежать в той части, где должна быть таблица векторов прерываний. Пока исполнительных устройств нет, может и ничего, но при их наличии — довольно неприятно.
                    0
                    Обучение это прежде всего формирование понимания и правильных навыков. Потом, в процессе реальной работы, когда важна скорость выдачи продукта, можно обвешиваться какими угодно IDE и бирюльками. Но на данном этапе, я считаю, только так…
                      0
                      При чём здесь IDE? В нормальной IDE вы можете использовать make для сборки, написать руками скрипт для линковщика и т. п.

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

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

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

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

                          Что до магических чисел. Я, например, как адепт старой школы, на автопилоте всем константам прописываю мнемонические названия.
                          Это не старая школа, это — обычный, но необходимый, навык. Присутствие магических чисел в коде — очень серьезный code smell.

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

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

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

                            Прямо убить контроллер — это, конечно, надо постараться. А вот из-за набитой с опечаткой чужой программки, как-то пришлось полдня потом промудохаться, чтобы привести контроллер в чувство. Было дело… Он был уже запаян на плату…
                        0
                        Сравнивая с habrahabr.ru/post/189484/ бирюльки в виде IAR появляются уже у вас :-) Впрочем я нисколько не дискредитирую вашу статью — тема разбора reference manual у вас раскрыта лучше.
                          0
                          Пардон, но лично я давно и счастливо сижу на Linux'e, и IAR стараюсь обходить стороной. Лично моё отношение к подобным IDE сугубо отрицательное. Правда, я не занимаюсь микроконроллерами профессионально. Для меня это хобби…

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

                            Кстати говоря, коллеги, которые занимаются эмбеддедами профессионально, комментируют все вот эти «сборка вручную» и «мейкфайлы» как игры в песочнице :-)
                              +2
                              Да. знаю таких товарищей. Они обычно от Linux'а шарахаются как от огня. Этим и объясняется их «снисходительность» к «сборкам вручную» и «мейкфайлам»…

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

                              Зато, послушаешь рассказы как глючат эти IAR'ы порой, это вообще пипец… Когда про цену на Keil/IAR'ы говорить начинаешь они тоже грустнеют… В общем всё говорит за то, что это больше вопрос привычки а не каких-то реальных профитов…
                                0
                                Моя информация, вероятно, устарела, но, вроде, в Keil uVision (могу путать с IAR, т. к. в то время работал в одном, делал хоббийные проекты в другом) для x51 в 2006, примерно, году не было:
                                — нормальной отладки (да-да, embedded среда),
                                — почти не было подсветки синтаксиса,
                                — навигации по коду (например, перейти к объявлению/реализации, перейти в заголовочный файл),
                                — автодополнения (имен файлов, названий функций, полей в структурах).

                                Про всякие фичи типа элементарного рефакторинга и говорить нечего.
                  +3
                  Ага, так вот кому надо сказать за перемещение arm-none-eabi-gcc и иже с ним в community repo… Спасибо… )
                    0
                    Присоединяюсь =)
                      +2
                      :D Завсегда пожалуйста, пользуйтесь на здоровье.
                    0
                    Я бы не рекомендовал напрямую работать с регистрами, особенно для новичков. У STM есть неплохая прослойка в виде библиотек, и читаемость кода выше, и портирование кода на другие версии процессора будет проще. Конечно полезно знать, что делает библиотечная функция, но совсем не обязательно это помнить — всегда можно посмотреть, открыв ее код или define
                      0
                      Ох, помню как я втыкал в архитектуру F1, а потом заказал себе F4, и долго пытался понять разницу работы и победить компилятор :)). Потом прозрел и читаю документацию можно сказать прямо в заголовках. Пишу на кокосе. Уже объяснил паре товарищей как работать с кокосом и программированием под микроконтроллеры. :) Один радиолюбитель был, какие же у него были азартные глаза когда он понял что он делает и что может сделать :)
                      А обучение мое довольно жестокое, грызи, грызи, грызи. Вот это глянь и пойми почему так, а не этак. Так что я за либы от stm как никак а переделки малы, и можно даже окружиться в своих либах дефайнами по платформе что бы компилилось и под f1 и под f4. Конечно не универсальное решение, но вполне сгодится для общих задач. ЗА статью определенно +, все таки описывает многие аспекты программирования для начинающих.
                        0
                        «Как видно из описания для совершения требуемой нам настройки необходимо записать значение 01b в 26-27 биты регистра GPIOx_MODER. Адрес регистра можно определить тем же методом, что описан выше.»
                        Помогите вкурить, как число можно в биты впихнуть?

                        Only users with full accounts can post comments. Log in, please.