STM32: Урок 4 — GPIO


GPIO (General Purpose Input-Output) — это выводы общего назначения, ноги микроконтроллера, доступные для прямого управления. Это обычно довольно дефицитный ресурс во многих популярных МК, но с STM32 эта проблема теряет актуальность: в самом мелком корпусе (LQFP48) доступно 37 GPIO, а в самом большом (LQFP176) — 140 GPIO. И всё это богатство ещё и настраивается вдоль и поперёк. Но, обо всём по порядку.

Для начала откроем руководство по STM32F100xx и взглянем на схему вывода порта:

Сами МК питаются 3.3 В, но до сих пор ещё активно используются 5-вольтовые микросхемы и логика, а их нужно как-то подключать. Поэтому в STM32 большинство выводов «толерантны» к 5 В — уж не знаю, как ещё перевести термин «5 V tolerant». То есть, они могут принимать на вход 5 В без какой-либо угрозы их здоровью.

Толерантный пин отличается от обычного только тем, что у него верхний защитный диод подключён к Vdd_ft вместо Vdd. Из этой схемы становится понятно, что хоть выводы и толерантны к 5 В на вход, но вот выдавать 5 В на выход не могут — тут уже нужен транзистор. Если нужно получить логическую единичку, то это не проблема — 3.3 В вполне распознаются 5-вольтовой логикой как 1, но если нужно именно 5 В, то есть решение — режим Open-drain у GPIO.

Всего у STM32F10x режимов GPIO имеется 8.

Выход общего назначения:

  • Push-pull, стандартный выход: выставляешь 0 в выходном регистре — получаешь низкий уровень на выходе, выставляешь 1 — получаешь высокий.
  • Open-drain (открытый сток, аналог открытого коллектора): вывод подключен к стоку N-MOS полевика в то время, как P-MOS полевик заперт, что позволяет управлять нагрузкой с большим напряжением, чем Vdd (3.3 В). Кому там нужно 5 В на выход? Ниже я расскажу, как их получить.

Выход с альтернативной функцией (для периферии типа SPI, UART):

  • Push-pull
  • Open-drain

Вход:

  • Analog, аналоговый высокоимпендансный: подтягивающие резисторы и триггер Шмитта отключены. Используется при работе с АЦП.
  • Floating, обычный высокоимпендансный: подтягивающие резисторы отключены, триггер Шмитта включен.
  • Pull-up, вход с подтяжкой к питанию.
  • Pull-down, вход с прижатием у к «земле».

Как водится, линии GPIO объединены в порты, в STM32 — по 16 линий, поэтому нумеруются они с 0 по 15: PA0, PA1 .. PA15 — это линии порта A, например. Линии порта управляются программно с помощью нескольких регистров.

GPIOx_CRL и GPIOx_CRH — регистры конфигурации, содержат настройки режима (вход/выход) и частоты GPIO. Доступны на чтение и запись.

GPIOx_IDR и GPIOx_ODR — входной и выходной регистры: в первом хранится считанное со входов порта значение, во второй записывается новое состояние выводов. GPIOx_IDR доступен только на чтение, а GPIOx_ODR — на чтение и запись.

GPIOx_BSRR и GPIOx_BRR — регистры атомарного изменения битов в GPIOx_ODR.
Обычно, если нужно установить бит в регистре периферии, то его сначала нужно прочитать, потом применить побитовое ‘ИЛИ’ к считанному значению и битовой маске, после чего записать новое значение назад в регистр. То же и со сбросом битов, только маску нужно инвертировать и применить побитовое ‘И’. А вот запись значений в GPIOx_BSRR и GPIOx_BRR изменяет только те биты выходного регистра, которые были установлены в единицу, притом происходит это за 1 такт, так что прерывание не сможет ворваться и всё испортить. Проиллюстрирую кодом:

const uint32_t mask = 1 << 4;
/* Установить 5й бит порта A, эквивалент: GPIOA_ODR |= mask */
GPIOA_BSRR = mask;
/* Сбросить 5й бит, эквивалент: GPIOA_ODR &= ~mask */
GPIOA_BRR = mask;

Оба регистра доступны только на запись.

Всё это я рассказал для общего развития, а мы пока абстрагируемся от этих деталей, и будем всё делать через библиотеку SPL - с регистрами возится на данном этапе не резон.

В структуре GPIO_InitTypeDef из SPL, которую мы использовали в предыдущем уроке для инициализации GPIO, за режим отвечает поле GPIO_Mode, а константы для его заполнения имеют следующие имена:

  • GPIO_Mode_Out_PP - выход push-pull
  • GPIO_Mode_Out_OD - выход open-drain
  • GPIO_Mode_AF_PP - альтернативная функция, push-pull
  • GPIO_Mode_AF_OD - альтернативная функция, open-drain
  • GPIO_Mode_AIN - аналоговый высокоимпендансный вход
  • GPIO_Mode_IN_FLOATING - высокоимпендансный
  • GPIO_Mode_IPU - вход с подтяжкой к питанию
  • GPIO_Mode_IPD - вход с прижатием к земле

Подсмотреть это и многое другое можно в заголовочном файле stm32f10x_gpio.h, а в руководстве можно узреть ещё много интересного, включая детальные описания режимов и таблицу с описанием режимов GPIO для разной периферии - UART, SPI, I2C и др.

Пример инициализации GPIO для модуля USART1 и самого модуля на STM32VLDiscovery:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef gpio;
GPIO_StructInit(&gpio);

/* TX: выход push-pull */
gpio.GPIO_Mode = GPIO_Mode_AF_PP;
gpio.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOA, &gpio);

/* RX: высокоимпендансный вход */
gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
gpio.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &gpio);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
USART_InitTypeDef usart;
/* Параметры по умолчанию: 9600 бод, 8 бит данных, 1 стоп-бит */
USART_StructInit(&usart);
USART_Init(USART1, &usart);
USART_Cmd(USART1, ENABLE);

Что касается режима Open-drain, тут всё не так прямолинейно, как с другими режимами. Так как устройство, которым предполагается управлять, подключено к открытому стоку, нужно чуть более сложное соединение, чем обычно, а управление будет инверсное: выставляешь выход в 1 - ток с верхнего резистора идёт на землю, устройство видит на линии 0; выставляешь 0 - полевик запирается, ток идёт на устройство, на линии уровень 1.

Разработчики STM32 также позаботились об энергопотреблении и о снижении уровня помех, предусмотрев настройку частоты: по сути, входной и в ыходной регистры GPIO тактируются от отдельного источника, что позволяет задать свою частоту каждой ножке МК. Для STM32F10x доступны 3 частоты, которые представлены константами:

  • GPIO_Speed_10MHz
  • GPIO_Speed_2MHz
  • GPIO_Speed_50MHz

Разумеется, частота GPIO не должна превышать частоту ядра (:

Ещё одна и интересная функция - переназначение выводов. Она позволяет переназначить выводы периферии с обычных на альтернативные, тоже фиксированные - впрочем, это не умаляет ценности данной функции: например, для USART1 можно переназначить TX с PA9 на PB6, а RX с PA10 на PB7. Если взглянуть на распиновку МК, можно увидеть, что обычные и альтернативные выводы находятся на разных сторонах кристалла, так что в разводке платы это в любом случае поможет:

А включается переназначение вот так:

/* Сначала нужно затактировать модуль AFIO,
 * управляющий альтернативными функциями.
 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
/* Переназначаем USART1: TX -> PB6, RX -> PB7 */
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);

Если вы два часа разводили двустороннюю плату, а потом обнаружили, что не можете развести какую-то сторону без огромной тучи переходов и перемычек, возможно, что функция переназначения выводов спасёт вашу клавиатуру от нелепой смерти под кулаком.

Есть у GPIO и довольно диковинная функция - блокирование выводов. Я так и не понял, зачем она может понадобится, но суть такова: можно заблокировать изменение состояния любого GPIO до следующей перезагрузки МК. Активируется тоже несложно:

/* Фиксируем состояния выводов PA6 и PA13 */
GPIO_PinLockConfig(GPIOA, GPIO_Pin_6 | GPIO_Pin_13);

Несколько слов об электрических характеристиках
В даташите по STM32F100xx встречаются рекомендации подключать не более 20 выводов с отдачей 8 мА через каждый или не более 8 выводов с 20 мА, но практически нереально найти информацию по максимальному току на вывод. Но есть таблица, где приведена максимальная рассеиваемая мощность для МК целиком, причём для разных корпусов эта мощность разная. Например, для LQFP64, в котором идёт STM32F100RBT6B на STM32VLDiscovery, эта мощность равна 444 мВт, что при напряжении питания 3.3 В даёт силу тока ~134 мА. В другой таблице указано, что максимальный ток, потребляемый МК в режиме выполнения кода со всей включенной периферией при 100℃, составляет 15.7 мА. Итого имеем 134 - 15.7 = 118.3 мА на все выходы. Это максимальный ток, который может пропустить через себя МК, что немного расходится с рекомендациями. Впрочем, питать что-либо кроме светодиодов от MК в любом случае - плохая идея, а 118.3 мА хватит на пару-тройку десятков обычных светодиодов, которые при номинальном токе в 20 мА выжигают глаза, а при 1 мА вполне годятся в индикаторы.


16 комментариев на «“STM32: Урок 4 — GPIO”»

    • Спасибо.
      Если не терпится прямо сейчас, то можешь использовать таймер SysTick — настроить его на частоту 10000 в секунду, а в обработчике прерывания уже счётчиков понаделать под сервы. Но в следующих статьях я буду описывать тактирование и таймеры, а таймеры у STM32 позволяют задавать произвольные делители, что для серв и т.п. очень удобно.

    • Упс, забыл поправить описание после того, как сменил схему. Лучше верну старую.

  1. На счет электрических характеристик.
    Я думаю ни какого противоречия в рекомендациях «не более 20 выводов с отдачей 8 мА через каждый или не более 8 выводов с 20 мА» и рассеиваемой мощностью 444 мВт нет.
    Если считать 8 x 20 x 3.3 = 528 мВт, но это же мощность если всё напряжение сАдится в 0 в МК, т.е. выводы замкнуты на землю. В реале при разумных токах нагрузки на выводы как рекомендуют до 20 мА, падение напряжения вряд-ли будет приближаться даже к 1-1.5 В. А это уже всего 160-240 мВт.

  2. Практически, идеальная статья!
    К примеру, я не знал для чего режим Open-Drain и как его юзать. Оказывается, это невероятно удобно!
    Спасибо тебе, burjui, за то, что нашел время все это описать!

    Вопрос. Как работает триггер Шмитта, и почему он применяется в USART’е в RX?

    • Собственно, этот триггер стоит на всех входах GPIO для надёжной фиксации состояния входа. Как известно, в цифровой электронике за ноль считается напряжение ниже некоторого порога V1, а за единицу — выше некоторого порога V2, причём V2 > V1. Между V1 и V2 лежит диапазон напряжений, которые ни за 0, ни за 1 не считаются, и состояние входа при таком напряжении меняться не должно — например, если вход ранее был в состоянии 0, а напряжение чуть-чуть превысило V1. Триггер Шмитта служит как раз этой цели: он переключается из 0 в 1 только при превышении напряжением на входе порога V2, а из 1 в 0 — только при падении напряжения ниже порога V1. Таким образом, небольшие колебания напряжения в районе V1 и V2 не будут приводить к постоянному переключению входа. Условно говоря, триггер Шмитта устраняет помехи.

      Насколько я понял из интернетов, этот триггер построен на компараторе с положительной обратной связью, но в этом я не разбираюсь, увы (:

    • Спасибо! Теперь прояснилось (:
      Это объясняет, почему аналоговый вход идет в обход триггера Шмитта.

  3. Статьи отличные! Очень ясное изложение. Спасибо! Жаль нет продолжения.
    Только про Open-drain похоже не совсем верно.Этот режим работает и без доп. транзистора.
    Просто выход подтягивается резистором к 5в. В этом режиме при записи 0 выход притягивается к земле, а при записи 1 отпускается (отключается), и напряжение на выходе 5в. А ещё этот режим позволяет объединять выходы по «И» (1w, i2c так работают).

  4. Ребят, только изучаю АРМ, подскажите кто знает максимальную скорость переключения вывода, у меня она ограничивается в 400 кГц.
    Плата STM32 Discovery, вот код:

    int main(void)
    {
    	  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    
    	  GPIO_InitTypeDef gpio;
    	  GPIO_StructInit(&gpio);
    	  gpio.GPIO_Mode = GPIO_Mode_Out_PP;
    	  gpio.GPIO_Pin = SPEED;
    	  gpio.GPIO_Speed = GPIO_Speed_10MHz;
    	  GPIO_Init(GPIOC, &gpio);
    
        while(1)
        {
        GPIO_Write(GPIOC, GPIO_ReadOutputData(GPIOC) ^ (SPEED));
        }
    }
    

    Делаю замер частоты на выводе SPEED — показывает 200кГц, а так как стоит ^(SPEED) — значт умножаем полученную частоту на 2, получаем 400кГц.

    • Я проверил — действительно, получается около 400 КГц. Но тут нет ничего странного, ведь нужно учитывать скорость выполнения программы и то, что функции не встраиваются (inline), если выключены оптимизации: каждый вызов функции вносит задержку. Так что у вас есть два варианта:

      1. Собирать Release-версию проекта со включенными оптимизациями (-O2 или -Os). Скорость переключения достигнет 1 МГц.

      2. Не использовать библиотечные функции, а писать так:

      while(1)
        GPIOA->ODR = GPIOA->ODR ^ SPEED;

      Скорость будет около 2 МГц.

      В любом случае, добиться скорости переключения вручную в 10 МГц невозможно. С другой стороны, таймер вполне может выжать все 10 МГц, т.к. у него переключение реализовано аппаратно.

    • Да, спасибо, через ODR скорость выросла до 1.6мГц, а если еще оптимизировать по О3 — то поднимается до 2.6мГц.
      А что за оптимизация Оs?, у меня в программе coocox есть варианты только О1, О2, О3, ну и конечно же NONE,

    • Это оптимизация размера кода, также включает в себя -O2. Если Linux есть, то смотри man gcc насчёт оптимизаций. Кстати, при использовании -O3 я бы рекомендовал отлаживать и тестировать код особенно тщательно, т.к. этот уровень предполагает использование потенциально небезопасных оптимизаций, которые могут привести к некорректной работе кода.

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

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Android Arduino Bluetooth CraftDuino DIY IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна конкурс манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение