Урок 7. Управление портами ввода-вывода через регистры CMSIS.

Уроки STM32

Научимся управлять портами через прямое обращение к регистрам.

Предыдущий урок     Список уроков     Следующий урок

В уроке 4 я писал, что главное преимущество прямого обращения к регистрам это скорость выполнения операций. Как удачный вариант я отметил способ, при котором начальная конфигурация формируется с помощью STM32CubeMX и библиотеки HAL, а данные в ходе выполнения программы передаются через регистры CMSIS. Начальные установки редко приходится менять при выполнении программы.

 

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

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

Для примера мы собираемся установить режимы работы:

  • для вывода PB12 – вход с подтягивающим резистором к шине питания;
  • для вывода PB13 – активный выход.

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

Для конфигурации порта GPIOB необходимо выполнить определенную последовательность действий.

Прежде всего, разрешить работу порта в регистре Разрешения тактирования периферийных устройств шины APB2 (APB2 peripheral clock enable register) RCC_APB2ENR. За порт B отвечает бит IOPBEN. Установим его в 1.

RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;

В принципе, эту операцию уже сделал за нас конфигуратор STM32CubeMX, когда мы выбрали активными  выводы порта B.

Биты конфигурации и режима для вывода PB12 расположены в регистре:

GPIOB_CRH, биты CNF12[1:0] и MODE12[1:0].

Смотрим по таблицам режима и конфигурации портов. Надо установить состояние:

CNF12[1:0], MODE12[1:0]  = 10, 00 (режим вход).

Теперь, как установить это состояние в регистрах конфигурации. Я на практике еще раз продемонстрирую то, о чем рассказывал в уроке 4.

 

В IDE открываем файл stm32f103xb.h. Он содержит имена регистров микроконтроллера.

Файл stm32f103xb.h

Структура для регистров портов в нем описана так.

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;

В ней все наши регистры для управления портами ввода-вывода, о которых мы говорили в предыдущем уроке.

Мы должны установить в регистре GPIOB_CRH, биты 19-16 в состояние 1000. Можно просто загрузить в регистр данное.

GPIOB->CRH = 0x00080000;

В этом случае мы сбросим все остальные биты. Что-то перестанет работать. Но если в загружаемом числе сформировать все биты этого регистра сразу, то способ вполне допустимый. Более того это самый быстрый и короткий вариант.

Если мы хотим затронуть только нужные  биты, то можно сделать так.

GPIOB->CRH &= 0xfff0ffff;  // сбрасываем биты 16-19
GPIOB->CRH |= 0x00080000;  // устанавливаем биты

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

Можно несколько упростить, используя операцию сдвига.

GPIOB->CRH &=  ~ ( 0b1111 << 16 ); // сбрасываем биты 16-19
GPIOB->CRH |= 0b1000 << 16;  // устанавливаем биты

Все вычисления с константами компилятор сделает на этапе трансляции и в правой части операций получатся точно такие числа, как и в предыдущем варианте.

В файле stm32f103xb.h есть имена и для битов регистра GPIOx_CRH. Вот, что касается 12го разряда.

/*******************  Bit definition for GPIO_CRH register  *******************/
#define GPIO_CRH_MODE12_Pos     (16U)
#define GPIO_CRH_MODE12_Msk    (0x3U << GPIO_CRH_MODE12_Pos)     /*!< 0x00030000 */
#define GPIO_CRH_MODE12              GPIO_CRH_MODE12_Msk               /*!< MODE12[1:0] bits (Port x mode bits, pin 12) */
#define GPIO_CRH_MODE12_0         (0x1U << GPIO_CRH_MODE12_Pos)     /*!< 0x00010000 */
#define GPIO_CRH_MODE12_1          (0x2U << GPIO_CRH_MODE12_Pos)     /*!< 0x00020000 */

#define
GPIO_CRH_CNF12_Pos         (18U)

#define GPIO_CRH_CNF12_Msk        (0x3U << GPIO_CRH_CNF12_Pos)      /*!< 0x000C0000 */
#define GPIO_CRH_CNF12                  GPIO_CRH_CNF12_Msk                /*!< CNF12[1:0] bits (Port x configuration bits, pin 12) */
#define GPIO_CRH_CNF12_0             (0x1U << GPIO_CRH_CNF12_Pos)      /*!< 0x00040000 */
#define GPIO_CRH_CNF12_1             (0x2U << GPIO_CRH_CNF12_Pos)      /*!< 0x00080000 */

Установка регистра конфигурации с использованием имен битов выглядит так.

GPIOB->CRH &=  ~ (GPIO_CRH_CNF12 | GPIO_CRH_MODE12); // сбрасываем биты полей CNF и MODE
GPIOB->CRH |= (0b10 << GPIO_CRH_CNF12_Pos) | (0b00 << GPIO_CRH_MODE12_Pos)  ;  // устанавливаем биты

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

Установим таким способом вывод PB13 в режим выхода. Для этого надо в поля CNF13[1:0], MODE13[1:0] установить   = 00, 10 (режим выход).

GPIOB->CRH &=  ~ (GPIO_CRH_CNF13 | GPIO_CRH_MODE13); // сбрасываем биты полей CNF и MODE
GPIOB->CRH |= (0b00 << GPIO_CRH_CNF13_Pos) | (0b10 << GPIO_CRH_MODE13_Pos)  ;  // устанавливаем биты

Теперь попробуем считать и установить состояния выводов. Давайте подключим к выводу PB12 кнопку, а к выводу PB13 светодиод.

Схема подключение светодиода и кнопки к STM32

И напишем программу, которая управляет светодиодом с помощью кнопки. Кнопка нажата – светодиод светится, отжата – светодиод погашен.

 

Считать состояние вывода можно через регистр ввода данных.

В структуре GPIO_TypeDef это регистр IDR. Проверяем 12й бит.

if( (GPIOB->IDR & (1 << 12)) == 0 ) {
  // бит 12 = 0, кнопка нажата

}
else {
  // бит 12 = 1, кнопка отжата

}

Для бита 12 регистра IDR есть имя, но оно не сильно улучшает читаемость программы.

/*!<******************  Bit definition for GPIO_IDR register  *******************/
#define GPIO_IDR_IDR12_Pos     (12U)
#define GPIO_IDR_IDR12_Msk    (0x1U << GPIO_IDR_IDR12_Pos)      /*!< 0x00001000 */
#define GPIO_IDR_IDR12               GPIO_IDR_IDR12_Msk                /*!< Port input data, bit 12 */

Используя имя бита можно написать:

if( (GPIOB->IDR & GPIO_IDR_IDR12) == 0 ) {

Теперь об установке логического состояния выхода. Есть несколько вариантов.

Первый – через регистр вывода.

Установка бита 13:

GPIOB->ODR |= 1 << 13;

или

GPIOB->ODR |= GPIO_ODR_ODR13;

Сброс бита 13:

GPIOB->ODR &= ~(1 << 13);

или

GPIOB->ODR &= ~ GPIO_ODR_ODR13;

Вставляем в логические блоки включение и выключение светодиода.

if( (GPIOB->IDR & GPIO_IDR_IDR12) == 0 ) {
  // бит 12 = 0, кнопка нажата
  GPIOB->ODR &= ~ GPIO_ODR_ODR13;  // зажечь светодиод
}
else {
  // бит 12 = 1, кнопка отжата
  GPIOB->ODR |= GPIO_ODR_ODR13;  // погасить светодиод
}

И всю эту конструкцию в цикл while(1) в файле main.c.

Компилируем, загружаем, проверяем. У меня работает.

 

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

Если нам надо установить вывод PB13  в 1, то:

GPIOB -> BSRR = GPIO_BSRR_BS13;  // установка бита

Для сброса бита надо выполнить команду:

GPIOB -> BSRR = GPIO_BSRR_BR13;  // сброс бита

Можно сбросить или установить сразу несколько битов одним обращением к регистру BSRR. Это будет выглядеть так.

GPIOB -> BSRR = GPIO_BSRR_BS10 | GPIO_BSRR_BS15 | GPIO_BSRR_BR11 | GPIO_BSRR_BR12; // установить биты 10 и 15, сбросить биты 11 и 12

Проверяем в программе.

if( (GPIOB->IDR & GPIO_IDR_IDR12) == 0 ) {
  // бит 12 = 0, кнопка нажата
  // GPIOB->ODR &= ~ GPIO_ODR_ODR13;  // зажечь светодиод
  GPIOB -> BSRR = GPIO_BSRR_BR13;  // сброс бита
}
else {
  // бит 12 = 1, кнопка отжата
  // GPIOB->ODR |= GPIO_ODR_ODR13;  // погасить светодиод
  GPIOB -> BSRR = GPIO_BSRR_BS13;  // установка бита
}

Еще для завершения конфигурации портов надо установить 1 в регистр вывода данных для бита PB12, настроенного на вход. Тем самым мы подключим подтягивающий резистор к шине питания, а не к земле.

GPIOB -> BSRR = GPIO_BSRR_BS12;  // подтягивающий резистор к + 3 В

Добавим эту строку в блок начальной конфигурации портов.

С регистром защиты LCKR разберетесь сами. Установка и сброс битов:

GPIOB -> LCKR |= GPIO_LCKR_LCK12 | GPIO_LCKR_LCK13;
GPIOB -> LCKR &= ~ (GPIO_LCKR_LCK110 | GPIO_LCKR_LCK11);
GPIOB -> LCKR |= GPIO_LCKR_LCKK;
GPIOB -> LCKR &= ~ GPIO_LCKR_LCKK;

Загрузить проект полностью можно по ссылке:

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Многим покажется,  что работать с портами на STM32 очень сложно. Особенно если сравнивать c аналогичными операциями в системе Ардуино. Но на Ардуино мы используем не прямое обращение, а функции. В следующем уроке будем работать с функциями управления портами на STM32. Все будет намного проще.

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

 

В следующем уроке будем управлять портами STM32 с помощью библиотеки HAL.

 

Предыдущий урок     Список уроков     Следующий урок

0

Автор публикации

не в сети 4 дня

Эдуард

280
Комментарии: 1936Публикации: 197Регистрация: 13-12-2015

28 комментариев на «Урок 7. Управление портами ввода-вывода через регистры CMSIS.»

  1. Здравствуйте. Заранее прошу прощение за глупый вопрос. Строка : GPIOB->CRH |= (0b00 << GPIO_CRH_CNF13_Pos) | (0b10 << GPIO_CRH_MODE13_Pos) ; // устанавливаем биты
    Символ и b в 0b00 что значит?

    0
  2. Здравствуйте, заранее прошу прощения за свою невнимательность (возможно вы об этом писали или уже отвечали кому-то в комментариях). Я лишь начинаю изучать программирование STM32 и мне не совсем ясно, почем вы всезда записываете значения а регистры не простым присвоением: «_GPIOx->CRL = 100000000», а так: «_GPIOx->CRL |= 0b1000<IDR & GPIO_IDR_IDR12) == 0 ) {«, предположим, GPIOB->IDR это 0100100100, GPIO_IDR_IDR12 это 0100000000, и далее применив побитово & получит 010000000, что с 1 сравнить невозможно, только если побитово каждый бит, что -то я тут запутался. Ещё раз простите за глупые вопросы. Спасибо!

    0
    • Здравствуйте!
      При операции присвоения вы обязательно установите состояние всех битов. Для того, чтобы изменить только нужные биты используются 2 последовательные операции: логическое ИЛИ (устанавливает выбранные биты в 1) и логическое И (сбрасывает выбранные биты).
      При сравнении для выделения нужных битов используется логическое И.
      if( (GPIOB->IDR & (1 < < 12)) == 0 ) Если 12й бит будет равен 0, то и выражение в скобках будет равно 0. Если он равен 1, то выражение будет иметь не нулевое значение.

      0
  3. Часть второго вопроса удалилась, речь шла о строчке if( (GPIOB->IDR & GPIO_IDR_IDR12) == 0 ) { Ещё был вопрос: почему так: if flag ==0 или flag!=0 , а не так: if flag ==0 или flag==1?

    0
    • Разницы особой нет. В первом случае значение истинно трактуется, как не нулевое. А во втором, как равное 1.

      0
  4. «И всю эту конструкцию в цикл while(1) в файле main.c.»
    Под «Всю конструкцию» подразумевается и та часть, где мы настраивали наши порты на Вход\выход? (работа с регистром CRH)
    Если да, то зачем эту часть помещать в цикл? CRH же не меняется в ходе работы программы. Т.е. достаточно один раз где-то задать и все
    Если нет, то, собственно, где задать эту часть с установкой СRH? Выше блока while в самом блоке Int main?

    0
    • Здравствуйте!
      Имеется в виду блок проверки состояния кнопки и управления светодиодом.
      while (1)
      {
      if( (GPIOB->IDR & GPIO_IDR_IDR12) == 0 ) {
      // бит 12 = 0, кнопка нажата
      // GPIOB->ODR &= ~ GPIO_ODR_ODR13; // зажечь светодиод
      GPIOB -> BSRR = GPIO_BSRR_BR13; // сброс бита
      }
      else {
      // бит 12 = 1, кнопка отжата
      // GPIOB->ODR |= GPIO_ODR_ODR13; // погасить светодиод
      GPIOB -> BSRR = GPIO_BSRR_BS13; // установка бита
      }

      0
  5. Эдуард, спасибо огромное. Ваши уроки это просто праздник какой-то. Вот здесь наверное очепятка:

    Еще для завершения конфигурации портов надо установить 1 в регистр вывода данных для бита PB12, настроенного на вход.
    ……..
    GPIOB -> BSRR = GPIO_BSRR_BS12; // подтягивающий резистор к + 3 В

    Видимо имелся в виду регистр ODR?

    0
    • Здравствуйте!
      Нет, все правильно. Нам надо установить только один бит в регистре ODR и делаем это с помощью регистра BSRR. Если мы применим такую операцию к ODR, то мы не только установим 12 бит, но и сбросим все остальные биты.

      0
  6. Здравствуйте, подскажите пожалуйста, обязательно ли в теле программы конфигурировать порты следующим кодом:
    // начальная конфигурация портов
    GPIOB->CRH &= ~ (GPIO_CRH_CNF12 | GPIO_CRH_MODE12); // сбрасываем биты полей CNF и MODE
    GPIOB->CRH |= (0b10 << GPIO_CRH_CNF12_Pos) | (0b00 <CRH &= ~ (GPIO_CRH_CNF13 | GPIO_CRH_MODE13); // сбрасываем биты полей CNF и MODE
    GPIOB->CRH |= (0b00 << GPIO_CRH_CNF13_Pos) | (0b10 < BSRR = GPIO_BSRR_BS12; // подтягивающий резистор к + 3 В

    Или будет достаточным сделать это только в в CubeMX?

    0
  7. Здравствуйте. Подскажите пожалуйста, почему вы выбрали именно такой способ подключения светодиода, можно ли в этом проекте было подключить анод в выводу 13 а катод к земле (как в проектах ардуино)?

    0
    • Здравствуйте!
      Да, конечно. Можно подключить светодиод на вытекающий ток. Считается, что втекающий ток идет по более короткому пути в микроконтроллере, вызывает меньший нагрев. Если число нагрузок (подключенных выходов) достаточно большое, то лучше использовать схему, которую применил я. Еще, у некоторых микросхем максимально допустимый втекающий ток больше, чем вытекающий.

      0
  8. Здравствуйте!

    У меня в файле stm32f407xx.h я нашёл «структуру для регистров портов» такого вида:

    typedef struct
    {
    __IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
    __IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
    __IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
    __IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
    __IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
    __IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
    __IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */
    __IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
    __IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
    } GPIO_TypeDef;

    Открыл даташит(190 страниц или это не он?) stm32f407 и там не нашёл регистров вот так красиво нарисованных как у Вас по 16, 32 бита, думал там хоть по-английски что-то будет написано.

    Я так понимаю, что уже в stm32f407 обращение к регистру CRL не получится так как его там нет? и вместо него уже MODER,OTYPER,OSPEEDR регистры? PUPDR- отдельный для подтяжки к питанию/земле…еще есть новый регистр AFR[2] и отсутствует BRR..

    Где искать описание для каждого регистра конкретного микроконтроллера STM(а то в рекламе была хорошая уловка о совместимости этих МК)?

    И есть ли это описание в программе STM32CubeMX? если есть то где там искать? (мне бы хотелось верить что должно быть, так-как при установке STM32CubeMX он подгрузил Гигабайт информации конкретно к моему МК).

    0
    • Здравствуйте!

      Я с этими микроконтроллерами не работал. Надо читать описание линейки STM32 F4. На официальном сайте должно быть.

      0
  9. Для тех кто тоже начнёт искать на st com. Найти нереально.

    файл называется RM0090: STM32F405/415, STM32F407/417, STM32F427/437 and STM32F429/439 advanced Arm®-based 32-bit MCUs

    В поиске нужно искать: DM00031020
    Reference manual называется, там 1749 страниц.

    0
  10. Нормально все излагаете, доступно.
    хотя новичкам в контроллерах и С наверняка непросто.

    0
  11. GPIOB -> BSRR = GPIO_BSRR_BS12; // подтягивающий резистор к + 3 В

    Здесь похоже ошибка. Подтягиваем в регистре GPIOB -> ODR.

    0
  12. Хочу поблагодарить за очень основательные пошаговые инструкции по работе с STM32, скажем, самые лучшие и понятные. Но у меня вопрос: почему все обходят USB порт, которій находится на борту отладочных плат, что с ним не так? Помоему только у Ардуинщиков я встречал его использование.

    0
    • Здравствуйте!
      Спасибо за добрые слова.
      Что касается встроенного USB, то с ним надо уметь работать. Часто проще использовать UART и конвертер интерфейсов.

      0
  13. Здравствуйте подскажите пожалуйста вот в этом куске кода:
    ‘Установка регистра конфигурации с использованием имен битов выглядит так.

    GPIOB->CRH &= ~ (GPIO_CRH_CNF12 | GPIO_CRH_MODE12); // сбрасываем биты полей CNF и MODE
    GPIOB->CRH |= (0b10 << GPIO_CRH_CNF12_Pos) | (0b00 <CRH» 0x80000, а разве мы не получим в итоге в «GPIOB->CRH» 0x20000?

    0
  14. Доброе время суток! Такой вопрос. Когда то давным-давно я активно занимался программированием 8 разрядных микроконтроллеров на макро ассемблере. Для установки одного или нескольких бит в регистре по маске использовал обычный макрос, и подобный макрос (по той же маске) использовал для сброса битов. И программировать удобно и исходники были очень читабельны (при подавлении тела macro). Маски всегда можно было найти в include файлах или определить самому.
    Что то подобное на C можно забодяжить?

    0

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

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

Нажимая кнопку "Отправить" Вы даёте свое согласие на обработку введенной персональной информации в соответствии с Федеральным Законом №152-ФЗ от 27.07.2006 "О персональных данных".