Программируем STM32F — откуда что берется?

Эта публикация появилась в результате попытки разобраться как всё устроено: как программировать порты в STM32; как определены наименования регистров и битов; где в файловой системе лежат файлы, где это все определено; как называются эти файлы; и как со всем этим запутанным хозяйством работать.

Допустим, мы создали и успешно смогли откомпилироват проект для STM32F100C4. Но в зубах завяз вопрос — откуда компилятор узнал про названия регистров и битов? Он же должен был их расшифровать в константы, но где лежит этот шифр?

Вот пример кода, как происходит инициализация периферийных устройств микроконтроллера:

#include <cmsis/stm32.h>
...

void SystemInit(void)
{
  RCC->APB2ENR |= RCC_APB2ENR_IOPSEN;
  GPIOC->CRH |= GPIO_CRH_MODE9_1 | GPIO_CRH_MODE8_1;
  GPIOC->CRH &= GPIO_CRH_CNF9 | GPIO_CRH_CNF8;
}

int main(void)
{
  ...
  SystemInit();
  ...

  while(1)
  {
    ...
  }
}

Следует обратить внимание вот на какие моменты. Во первых, в начале кода указывается команда препрцессору подключить файл cmsis/stm32.h.

Во вторых, незатейливая функция SystemInit полностью состоит из имен регистров и названий их битов. Ну, в том смысле, что здесь нет переменных, которые мы сами определили.

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

Я самостоятельно собирал и устанавливал тулчейн (кросс-компилятор и сопутствующие средства) для ARM/Cortex. Я определил его резидентное размещение в директорий

/opt/arm/arm-none-eabi

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

bin —  для исполняемых программ (компилятор, линковщик, и так далее )
include — для размещения, соответственно, include-файлов
lib — для стандартной С-библиотеки и других нужных библиотек.

Там есть еще три поддиректория (share. shared, sys-include), но сейчас  мы не будем их касаться.

Так вот, «расшифровка» имен регистров и битов лежит тут

/opt/arm/arm-none-eabi/include/cmsis/stm32.h

В этом файле со строки 873-ей начинается карта памяти. Так например, в строках 880, 881 определены начальный адрес оперативной памяти и общий базовый адрес периферийных устройств (правда, комментарии разработчики слегка попутали, но это сути не меняет!):

#define SRAM_BASE             ((uint32_t)0x20000000) /*!< Peripheral base address in the bit-band region */
#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< SRAM base address in the bit-band region */

Чуть дальше, мы видим строки определения базовых адресов для периферийных устройств, которые разнесены по трем шинам:

#define APB1PERIPH_BASE       PERIPH_BASE
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)

Прокручиваем еще ниже, и на 914-ой строке видим определение базовых адресов портов ввода-вывода:

#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE            (APB2PERIPH_BASE + 0x1000)

, а на строке 990 видим:

#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)

Теперь GPIOC — это не просто какой-то абстрактный адрес (чисто), а целая структура (тип — GPIO_TypeDef), которая транспонируется на этот адрес.

Описание структуры мы находится выше, на строке 615:

typedef struct
{
  __IO uint32_t CRL;
  __IO uint32_t CRH;
  __IO uint32_t IDR;
  __IO uint32_t ODR;
  __IO uint32_t BSRR;
  __IO uint32_t BRR;
  __IO uint32_t LCKR;
} GPIO_TypeDef;

Ага! Значит, структура состоит из 7-ми простых 32-х битных сущностей, которые расположены в памяти последовательно друг за другом. Это регистры. Да, наконец-то мы добрались до них!

Теперь мы знаем, что представляет из себя выражение GPIOC->CRH.

Но мы все еще не знаем, про биты. С ними-то что?

Прокручиваем файл на строку 1518, где начинается определение имен битов. Нас иетересует конкретно имя GPIO_CRH_MODE9_1. Блок, посвященный девятому биту порта С начинается со строки 1594:

#define  GPIO_CRH_MODE9                      ((uint32_t)0x00000030)        /*!< MODE9[1:0] bits (Port x mode bits, pin 9) */
#define  GPIO_CRH_MODE9_0                    ((uint32_t)0x00000010)        /*!< Bit 0 */
#define  GPIO_CRH_MODE9_1                    ((uint32_t)0x00000020)        /*!< Bit 1 */

Ага! Теперь мы знаем, чему равно значение бита GPIO_CRH_MODE9_1. Заметьте, что окмбинация по ИЛИ обоих бит GPIO_CRH_MODE9_0 и GPIO_CRH_MODE9_1 тоже определена и имеет имя GPIO_CRH_MODE9.

Что касается определения бита GPIO_CRH_CNF9, то он пределен в блоке на строке 1628:

#define  GPIO_CRH_CNF9                       ((uint32_t)0x000000C0)        /*!< CNF9[1:0] bits (Port x configuration bits, pin 9) */
#define  GPIO_CRH_CNF9_0                     ((uint32_t)0x00000040)        /*!< Bit 0 */
#define  GPIO_CRH_CNF9_1                     ((uint32_t)0x00000080)        /*!< Bit 1 */

С расшифровкой 8-го бита порта С, я думаю, у вас уже не возникнет проблем.

Теперь попробуем сообразить, как нам следует написать код, чтобы проинициализировать 9-ый бит порта С.

Допустим, мы хотим его развернуть на ввод. Нам нужно сбросить оба бита MODE9 в регистре GPIOC->CRH. Делается это так:

  GPIOC->CRH &= ~GPIO_CRH_MODE9;

Мы взяли два бита (GPIO_CRH_MODE9) — то есть это 32-битное число, у которого два бита установлены в единичное состояние, а остальные биты сброшены в нулевое. Затем мы взяли и инвертировали это число (операция ‘~’), в результате получилось другое 32-битное число, у котрого два бита находятся в нуле, а остальные в единичном сотсоянии. Затем, мы считываем то, что записано в регистре CRH (полный доступ к нему — GPIOC->CRH), и обединяем наше число и это значение по «И» (операция «&=»), и полученный результат тут же записываем обратно в регистр CRH. Таким образом, мы обнулили только два бита — GPIO_CRH_MODE9_0 и GPIO_CRH_MODE9_1

Сложнее дело остоит когда нам нужно запрграммировать порт на вывод на скорость 2 или 10 МГц. Для скорости 2 МГц, мы должны бит GPIO_CRH_MODE9_0 сбросить, а бит GPIO_CRH_MODE9_1 — установитьть. Быстро (за одну перацию) это не выполнить. Придется дважды обращаться к регистру CRH.

  GPIOC->CRH &= ~GPIO_CRH_MODE9_0;
  GPIOC->CRH |=  GPIO_CRH_MODE9_1;

Для программирования на скорость 10 МГц коду будет чуть-чуть другим:

  GPIOC->CRH |=  GPIO_CRH_MODE9_0;
  GPIOC->CRH &= ~GPIO_CRH_MODE9_1;

Все, что мы рассматривали до сих пор — это всй фигня! Интереснее будет, когда нам понадобится сконфигурировать бит порта на выход на скорость 2 МГц и с открытым стоком. Как развернуть порт на выход и настроить его на скорость 2МГц, мы уже умеем. Теперь, попробуем задать ему режим работы с открытым стоком. Для этого нам понадобятся биты CNF, причем, для обеспечения заданной конфигурации мы должны бит CNF9_0 установть в единицу, а бит CNF9_1 сбросить. То есть, код должен быть таким:

  GPIOC->CRH |=  GPIO_CRH_CNF9_0;
  GPIOC->CRH &= ~GPIO_CRH_CNF9_1;

Но чтобы

«два раза не вставать» (с) депутатское

то есть не обращаться четыре раза к регистру CRH — два раза сначала для установки битов GPIO_CRH_MODE9_1 и GPIO_CRH_CNF9_0, а потом еще два раза для сброса битов GPIO_CRH_MODE9_0 и GPIO_CRH_CNF9_1, то давайте обединим эти операции:

  GPIOC->CRH |=  (GPIO_CRH_CNF9_0 | GPIO_CRH_MODE9_0);
  GPIOC->CRH &= ~(GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9_1);

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

Если у вас еще не поехала крыша от этого беспредела, то я вас поздравляю! Вам, в общем-то, не нужны никакие библиотеки периферийных устройств типа stm32_perif и cmsis. Вы сами справитесь с задачей обращения к регистрам периферийных устройств.

Да, это сложно. Запутано. Может юыть, где-то даже и не рационально. Но, на сколько я понимаю, Cortex-ы — это все-таки числодробилки, а не ногдрыгалки. Основная задача числодробилок — перелопачивать данные, обрабатывать результаты измерений, заниматься тяжелыми расчетами, а не ногами «махать». Значит, инициализация (настртройка) периферийных устройств — это одноразовая операция. Настроил и забыл. Потом только тяжелыми рассчетами и занимаешься. Иняче говоря, придется потерпеть «инициализацию», но зато потом работать — песня!

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

Поэтому, наличие библиотек stm32_perif и cmsis сильно облегчает программирование Cortex-ов. За «комфорт» мы расплачиваемся повышенным расходом флешь-памяти, не оптимальностью кода, снижением быстродействия, и что самое печальное — полным непрорубоном того, что там под капотом происходит. Вся эта ситуация напоминает блондинку за рулем — с одной строны, розовый автомобильчик вроде как и едет, но с другой — почему-то из трубы валит черный дым и чой-то там, под капотом цокает.

2 responses to “Программируем STM32F — откуда что берется?

  1. //Будьте первым, кому это понравилось.//
    Понравилось. Очень. Я новичок в STM32 и мне очень не хватало понимания «откуда что берется». Спасибо, всё стало на свои места.

    • Спасибо, Alex, за теплые слова.

      //Будьте первым, кому это понравилось.// — это не мое «творчество». Это, а также всякую рекламу «лепит» движок портала wordpress.com без моего участия. Я всего-лишь «квартируюсь» со своим блогом не его площадях.

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

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s