В этом уроке вы узнаете как настраивать порты ввода-вывода (General I/O Ports, GPIO) для написания супер быстрого и супер маленького кода. Научитесь устанавливать состояние выходов и читать состояние входов. А еще узнаете, как настроить все GPIO очень быстро и без ошибок. Используйте мои уроки для того, чтобы понимать, что вы делаете. Не отказывайтесь от CubeMX полностью. Он будет помогать вам сделать разработку еще быстрее. А знания разработки под CMSIS помогут сделать быстрее ваши программы.
Если вам нравится то, что я делаю и вы хотите следить за новыми статьями - станьте подписчиком Telegram канала ipaSoft Electronics. Чем нас больше, тем я больше стараюсь!
Все GPIO порты в STM32F103 тактируются шиной APB2. Перед использованием какого-либо GPIO необходимо сначала разрешить его тактирование, а лишь потом приступать к настройке. Так мы и поступим на примере GPIOB. Пусть PIN1 будет выходом, а PIN0 будет входом.
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // Включить тактирование GPIOB
// PB0 - Input floating (CNF0=01 MODE0=00), PB1 - Output (CNF1=00 MODE1=01);
GPIOB->CRL |= GPIO_CRL_MODE1_0;
GPIOB->CRL &= ~GPIO_CRL_CNF1_0;
Здесь, для настройки младших 8 линий порта (Px0..Px7) используется регистр CRL, но для настройки старших линий порта (Px8..Px15), будет использоваться регистр CRH. Регистр CRx – это 32-битный регистр, в котором чередуются биты конфигурации: 2 бита MODE0, 2 бита CNF0, 2 бита MODE1, 2 бита CNF1 и так далее.


Обратите внимание, что по умолчанию все линии настроены как Floating Input (везде CNFx = 01), поэтому для PB0 настройки остаются по умолчанию, а для PB1 прописываем новые. По этой таблице, можно так настроить любую линию любого порта.
Существует один момент, на микроконтроллерах STM32F1 в корпусах с 36, 48, 64 выводами PD0 и PD1 используются для подключения внешнего кварца по умолчанию, и чтобы использовать PD0 и PD1 как GPIO, нужно дополнительно настроить REMAP (AFIO_MAPR).
RCC->APB2ENR |= RCC_APB2ENR_IOPDEN; // Включить тактирование GPIOD
__NOP();
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; // Разрешить тактирование REMAP
__NOP();
AFIO->MAPR |= AFIO_MAPR_PD01_REMAP; // REMAP пин PD01
Чтение и запись GPIO
Входы GPIO не могут самостоятельно устанавливать выходной сигнал, они лишь подключаются к какому-то источнику сигнала, например к кнопочке. Со входа можно только читать состояние. Набор состояний всех линий порта представлен в регистре GPIOx->IDR (Input data register). Для линии 0 — бит 0, для линии 1 — бит 1, и так далее.
Выходы GPIO могут устанавливать выходной сигнал, это либо 0 (нет напряжения, замыкание на общий провод питания), либо 1 (в режиме Push-pull: есть напряжение, примерно равное напряжению питания микроконтроллера; в режиме OpenDrain: просто перестает замыкать линию на общий провод). Чтобы подать логическую единицу на выход, нужно записать в регистр GPIOx->ODR (Output data register) соответствующий бит. Для линии 0 — бит 0, для линии 1 — бит 1, и так далее.
Допустим, у нас есть кнопка, при нажатии на которую на линию порта PB0, через нее подается напряжение +3.3 Вольта. А к PB1 подключен светодиод через токоограничивающий резистор.
Задача: сделать так, чтобы по нажатии кнопки, начинал светиться светодиод. В бесконечном цикле внутри main() пишем следующее
while(1) { // Бесконечный главный цикл
if (GPIOB->IDR & 0x01) GPIOB->ODR |= 0x02; // Установить бит 1
else GPIOB->ODR &= ~0x02; // Сбросить бит 1
}
или еще такой, более оптимизированный вариант:
while(1) { // Бесконечный главный цикл
if (GPIOB->IDR & 0x01) GPIOB->BSRR = 0x02;
else GPIOB->BSRR = 0x02 << 16;
}
Обращение через регистр GPIOx->BSRR позволяет устанавливать или сбрасывать биты по одному или группой без операции прочтения состояния ODR. Первые 16 бит (15..0) устанавливают линии в единицу, последние 16 бит (31..16) сбрасывают их. Запись 0x02 в BSRR установит только бит #1 не затрагивая другие биты.
А еще один маленький секрет, который ни для кого не секрет. Выход в режиме Open-Drain можно использовать и как вход, не переключая режим. Если на такой выход вы подаете 0, то ясное дело, там может быть только 0. Но если вы подаете лог. 1 то вы не знаете, какое состояние у линии, но можете узнать через GPIOx->IDR. Мне это пригодилось при программировании интерфейса 1-Wire, где линия синхронная DQ предназначена для записи и чтения одновременно. Вы можете прочесть статью по работе с датчиками 1-Wire DS18B20 на этом сайте здесь
И еще один секрет. Как правило, на входы АЦП и некоторых линий GPIO по документам нельзя подавать больше VDD + 0.3V. Кроме того есть часть пинов, толерантных к TTL 5V уровню, на которые можно подавать (опять же по документам) до 5.5 Вольт. Если вы хотите прочесть сигнал большего напряжения, чем допустимо, то наверняка начнете использовать преобразователь уровня или делитель напряжения. Но не стоит бояться подавать напряжение выше допустимого, делайте это через резистор, без всяких стабилитронов, аттенюаторов, микросхем-преобразователей. Вольт 100 можно спокойно подавать через резистор 100k, а 12 Вольт через 10k. Излишки напряжения обязательно уйдут в источник питания, при этом линия порта микроконтроллера не повредится, на ней будет стабильно VDD + 0.3V, если линия не толерантна или около 9 Вольт, если толерантна к уровню 5V TTL. Узнать толерантность можно в Datasheet на микроконтроллер в таблице с описанием выводов. Аббревиатура FT - это и есть толерантность к уровню 5В.

Когда вы жестко оптимизируете свой код (а скорее всего, по этой причине вы сейчас и изучаете этот низкий уровень), то целесообразно настраивать режимы всех пинов, всех портов одной операцией, не дробя на периферию. Что-то вроде этого:
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // Включить тактирование GPIOA
__NOP();
GPIOA->CRL = 0x00004B00;
GPIOA->CRH = 0xB44444BB;
В этом примере скопом настроен GPIOA, чтобы к нему уже можно было не возвращаться. Я не настаиваю на именно таком способе «оптовой» настройки, а лишь подчеркиваю, что самая аскетичная оптимизация делается именно так. Более того, у меня есть пример из опыта, где инициализация определенной конфигурации с помощью генератора STM32CubeMX на HAL занимала 7,5 кБ с оптимизацией Oz, а то же самое на CMSIS заняло лишь 2,7 кБ с той же оптимизацией.
В завершение этого раздела хотел бы поделиться «грязным» лайфхаком. Хотите супер быстро сконфигурировать и GPIO, AFIO? Сгенерьте конфигурацию с STM32CubeMX, запустите отладку в Keil'е, перейдите к просмотру системных регистров и скопипастьте конфигурацию (значения регистров).

Пока всё. Спасибо, что читаете и учитесь.
Обсудить этот урок можно здесь, в Telegram канале
