Одним из способов формирования начальной конфигурации является использование STM32CubeMX и библиотеки HAL. Для передачи данных в программе можно использовать регистры CMSIS. Обычно начальные настройки остаются неизменными в течение выполнения программы.
С портами ввода-вывода все более сложно. Их использование затрудняется как высокочастотными сигналами, так и необходимостью оперативно изменять режимы работы. В определенных случаях, особенно при двунаправленных сигналах, может потребоваться быстрое изменение порта на режим входа или выхода.
В связи с этим, прямое управление портами ввода-вывода через доступ к регистрам является широко используемым методом.
Для примера мы собираемся установить режимы работы:
Позже подключим к этим выводам кнопку и светодиод, напишем управляющую программу.
Для конфигурации порта 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 (режим вход).
Установка состояния в регистрах конфигурации.

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

Установка регистра конфигурации с использованием имен битов выглядит так.
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 светодиод.

И напишем программу, которая управляет светодиодом с помощью кнопки. Кнопка нажата – светодиод светится, отжата – светодиод погашен.
Считать состояние вывода можно через регистр ввода данных.
В структуре GPIO_TypeDef это регистр IDR. Проверяем 12й бит.

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

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

И всю эту конструкцию в цикл 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
Проверяем в программе.

Еще для завершения конфигурации портов надо установить 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;
Автор: Эдуард