Программирование STM32. Часть 5: Порты ввода-вывода GPIO
Содержание
В этой части мы разберемся с порами ввода-вывода GPIO микроконтроллера STM32F103C8 и напишем Hello, World! с мигающим светодиодом, а так же научимся читать состояние выводов микроконтроллера и использовать встроенный подтягивающий резистор. Предыдущая статья здесь, все статьи цикла можно посмотреть тут: http://dimoon.ru/category/obuchalka/stm32f1.
Введение
General-purpose input/output (GPIO) — важный компонент любого микроконтроллера, с помощью которого он взаимодействует с окружающим миром. В микроконтроллерах STM32 порты именуются буквами A, B, C и так далее: GPIOA, GPIOB, GPIOC... Каждый GPIO имеет 16 линий ввода/вывода, причем каждая линия может быть настроена независимо от других. Вот варианты настройки:
- Input floating — вход с отключенными подтягивающими резисторами
- Input pull-up — вход с подтяжкой к логической единице
- Input-pull-down — вход с подтяжкой к логическому нулю
- Analog — аналоговый вход (например, для АЦП)
- Output open-drain — выход с открытым коллектором (записали 1 — выход в высокоимпедансном состоянии, записали 0 — выход прижат внутренним транзистором к земле)
- Output push-pull — выход тяни-толкай (записали 1 — на выходе лог. 1, записали 0 — на выходе лог. 0)
- Alternate function push-pull — альтернативная функция в режиме тяни-толкай
- Alternate function open-drain — альтернативная функция в режиме открытого коллектора
Дам несколько пояснений по поводу режимов.
Режим Analog. Внутри микроконтроллера есть аналогово-цифровые преобразователи, которые, как известно, должны иметь аналоговые входы. Так вот, в режиме Analog ножка микроконтроллера подключается к аналоговому входу АЦП внутри микроконтроллера. Кроме того, отключается вся цифровая обвязка этой ножки для уменьшения цифрового шума и энергопотребления.
Alternate function. В этом режиме ножкой микроконтроллера управляет внутренняя цифровая периферия, например, модуль USART.
Регистры GPIO
Давайте рассмотрим регистры портов GPIO.
Port configuration register low (GPIOx_CRL)
Рис. 1. Регистр CRL
Это конфигурационный регистр для выводов порта с номерами от 0 до 7. Каждому выводу предоставлено 4-ре бита конфигурации: 2 бита MODEy и 2 бита CNFy.
MODEy[1:0]: Режим ножки порта, вход или выход. В режиме выхода нужно выбрать максимальную частоту переключения данной ножки, насколько понял это является оптимизацией энергопотребления порта.
- 00: Вход (значение после сброса)
- 01: Выход, максимальная частота 10 MHz.
- 10: Выход, максимальная частота 2 MHz.
- 11: Выход, максимальная частота 50 MHz.
CNFy[1:0]: Конфигурация режима.
В режиме входа (MODEy[1:0]=00):
- 00: Analog mode — аналоговый режим (подключен к АЦП или ЦАП-у)
- 01: Floating input — вход с отключенными подтягивающими резисторами (значение после сброса)
- 10: Input with pull-up / pull-down — вход с подтяжкой вверх или вниз
- 11: Reserved — не используется
В режиме выхода (MODEy[1:0]00):
- 00: General purpose output push-pull — выход в режиме тяни/толкай
- 01: General purpose output Open-drain — выход с открытым коллектором
- 10: Alternate function output Push-pull — выход альтернативной функции режиме тяни/толкай
- 11: Alternate function output Open-drain — выход альтернативной функции с открытым коллектором
Port configuration register high (GPIOx_CRH)
Рис. 2. Регистр CRH
Это конфигурационный регистр для выводов порта с номерами от 8 до 15. Тут все то же, что и для регистра GPIOx_CRL.
Port input data register (GPIOx_IDR)
Рис. 3. Регистр IDR
IDRy: в этих битах содержится входное значение соответствующего порта ввода-вывода.
Port output data register (GPIOx_ODR)
Рис. 4. Регистр ODR
ODRy: выходные данные порта.
Port bit set/reset register (GPIOx_BSRR)
Рис. 5. Регистр BSRR
С помощью этого регистра можно сбросить или установить любой бит регистра ODR без операций чтение-модификация-запись.
BRy: Сбросить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)
- 0: не оказывает влияние на соответствующий бит ODRx
- 1: Сбрасывает в ноль соответствующий бит ODRx
BSy: Установить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)
- 0: не оказывает влияние на соответствующий бит ODRx
- 1: Устанавливает в единицу соответствующий бит ODRx
Port bit reset register (GPIOx_BRR)
Рис. 6. Регистр BRR
С помощью этого регистра можно сбросить любой бит регистра ODR без операций чтение-модификация-запись.
BRy: Сбросить бит у регистра ODR порта ввода-вывода (y= 0 .. 15)
- 0: не оказывает влияние на соответствующий бит ODRx
- 1: Сбрасывает в ноль соответствующий бит ODRx
Port configuration lock register (GPIOx_LCKR)
Рис. 7. Регистр LCKR
Этот регистр используется для блокировки конфигурационных битов порта после записи корректной последовательности в 16 бит (LCKK) регистра. Значения битов [15:0] используется для блокировки конфигурации GPIO. Во время блокирующей последовательности в LCKK значения LCKR [15: 0] не должны меняться. Когда блокирующая последовательность была записана, конфигурация выбранных портов ввода/вывода может быть изменена только после сброса микроконтроллера. Каждый LCKy бит блокирует возможность изменения четырех битов конфигурации порта (CRL, CRH).
LCKK[16]: Ключ блокировки.
- 0: Блокировка конфигурации порта не активна.
- 1: Блокировка конфигурации порта активна. GPIOx_LCKR заблокирован до следующего сброса микроконтроллера.
Для блокировки необходимо выполнить следующую последовательность:
- Записать 1
- Записать 0
- Записать 1
- Прочитать 0
- Прочитать 1 (эта операция чтения не является обязательной, а всего лишь подтверждает успешность установки блокировки)
LCKy: Эти биты могут быть прочитаны и записаны, но запись можно произвести только если бит LCKK равен нулю.
- 0: Конфигурация пина номер y не заблокирована
- 1: Конфигурация пина номер y заблокирована
Настройка порта GPIO
Итак, с регистрами разобрались, настало время практики. Все примеры в этой статье для микроконтроллера STM32F103C8. В моем распоряжении есть вот такая отладочная плата:
На ней установлен кварцевый резонатор на 8 МГц и светодиод на порту PB12. Вот с помощью этого светодиода мы и устроим Hello, World! 😉
Задача ясна: настраиваем PB12 на выход в режиме push-pull и с помощью регистра ODR дергаем 12-й пин порта GPIOB туда-сюда! Но мы забыли об одной маленько детали: RCC. Дело в том, что по-умолчанию после сброса микроконтроллера все периферийные модули отключены от источника тактового сигнала, в том числе и GPIO. А подать тактирование можно с помощью регистров RCC. В 3-ей части я про это говорил. Для начала нужно определить, к какой шине у нас подключен GPIOB. Открываем даташит на микроконтроллер, ищем вот эту таблицу:
Рис. 8. Таблица шин и периферийных устройств
GPIOB у нас подключен к шине APB2. Идем в Reference manual, открываем раздел про RCC, переходим к пункту 7.3.7 APB2 peripheral clock enable register (RCC_APB2ENR). С помощью этого регистра можно подать тактовый сигнал на устройства шины APB2:
Рис. 9. Регистр RCC_APB2ENR
В регистре RCC_APB2ENR много флагов для разной периферии, в том числе и для нашего GPIOB, флаг называется IOPBEN. Перед началом инициализации PB12 нам надо установить этот бит в единицу.
Поехали программировать! За основу возьмем проект из 2-й части: https://github.com/DiMoonElec/stm32f103c8_empty_project. Создадим функцию инициализации порта:
Первым делом включаем тактирование порта GPIOB:
Далее настройка порта. Нам нужен пин PB12. Его конфигурационные биты находятся в регистре CRH:
Все 😉 Настройка завершена! Вот полный код функции:
Теперь управление. Для этого нам понадобится регистр ODR (рис. 4). Вот функция установки высокого уровня на PB12:
Ни чего сложного, почти как на AVR-ках! Однако, у нас все же более серьезный микроконтроллер, и у него есть некоторые фишки. Выражения вида GPIOB-ODR |= (112) являются операциями чтение-модификация-запись. Это долго, и имеет побочные эффекты при одновременном обращении к одному и тому же регистру из разных участков программы (например, из прерывания и основного потока программы). Это называется нарушение атомарности операции. Но в STM32 можно сделать вот так:
Конструкции (112) превращаются в битовую маску 0x1000 на этапе компиляции, и у нас получается только одна операция записи в регистр BSRR или BRR (см. рис. 5, 6).
Ни и простой main() для проверки:
Все, светодиод, подключенный к BP12 мигает! Hello, World! готов!
Давайте теперь настроим какой-нибудь вывод порта, например PB15, на вход с подтяжкой к питанию. При подключении PB15 к минусу, у нас будет зажигаться светодиод. Задача ясна, преступаем к реализации. В PortInit() добавим пару строк:
Тут все почти то же самое, как и при настройке PB12, только другой пин и параметры MODE/CNF. А вот последняя строчка требует пояснений. Дело в том, что регистр ODR имеет двойное назначение. Если пин настроен на выход, то соответствующий бит в ODR отвечает за значение логического уровня на этом пине. Но если пин настроен на вход, причем CNF=10 (Input with pull-up / pull-down), то соответствующий бит в ODR управляет подтягивающими резисторами этого пина. К стати, в Reference manual есть вот такая картинка:
Рис. 10. Таблица конфигурации порта
Функция PortInit() приобретает такой вид:
Перейдем к чтению состояния PB15. Для этого существует регистр IDR (рис. 3):
Думаю тут все понятно: если 15 бит в регистре IDR равен 1, то возвращаем единицу, а иначе — ноль.
В этом случае main() будет выглядеть вот так:
На отладочной плате с микроконтроллером stm32f103c8 светодиод зажигается низким уровнем на выводе PB12. Это значит, что при замыкании PB15 на землю, будет прочитано нулевое значение на этом выводе и установлен ноль на PP12, и светодиод зажжется. Если PB15 отключить от земли, то внутренняя подтяжка прижмет вывод к плюсу питания микроконтроллера, на PB15 функция ReadPort() прочтет логическую единицу и на выводе PB12 возникнет лог. 1, что приведет к погасанию светодиода.
Источник: