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”»
Хорошая статья (:robocraft.ru/blog/mechanics/240.html , нужно отмерять задержки с помощью таймера в миллисекундах. Подскажи оптимальный вариант!
Как лучше реализовать работу с сервами? Судя по —
Спасибо.
Если не терпится прямо сейчас, то можешь использовать таймер SysTick — настроить его на частоту 10000 в секунду, а в обработчике прерывания уже счётчиков понаделать под сервы. Но в следующих статьях я буду описывать тактирование и таймеры, а таймеры у STM32 позволяют задавать произвольные делители, что для серв и т.п. очень удобно.
«выставляешь выход в 1 — ток с верхнего резистора идёт на землю»
О каком резисторе идет речь? на рисунке вроде диоды
Упс, забыл поправить описание после того, как сменил схему. Лучше верну старую.
На счет электрических характеристик.
Я думаю ни какого противоречия в рекомендациях «не более 20 выводов с отдачей 8 мА через каждый или не более 8 выводов с 20 мА» и рассеиваемой мощностью 444 мВт нет.
Если считать 8 x 20 x 3.3 = 528 мВт, но это же мощность если всё напряжение сАдится в 0 в МК, т.е. выводы замкнуты на землю. В реале при разумных токах нагрузки на выводы как рекомендуют до 20 мА, падение напряжения вряд-ли будет приближаться даже к 1-1.5 В. А это уже всего 160-240 мВт.
Практически, идеальная статья!
К примеру, я не знал для чего режим Open-Drain и как его юзать. Оказывается, это невероятно удобно!
Спасибо тебе, burjui, за то, что нашел время все это описать!
Вопрос. Как работает триггер Шмитта, и почему он применяется в USART’е в RX?
Собственно, этот триггер стоит на всех входах GPIO для надёжной фиксации состояния входа. Как известно, в цифровой электронике за ноль считается напряжение ниже некоторого порога V1, а за единицу — выше некоторого порога V2, причём V2 > V1. Между V1 и V2 лежит диапазон напряжений, которые ни за 0, ни за 1 не считаются, и состояние входа при таком напряжении меняться не должно — например, если вход ранее был в состоянии 0, а напряжение чуть-чуть превысило V1. Триггер Шмитта служит как раз этой цели: он переключается из 0 в 1 только при превышении напряжением на входе порога V2, а из 1 в 0 — только при падении напряжения ниже порога V1. Таким образом, небольшие колебания напряжения в районе V1 и V2 не будут приводить к постоянному переключению входа. Условно говоря, триггер Шмитта устраняет помехи.
Насколько я понял из интернетов, этот триггер построен на компараторе с положительной обратной связью, но в этом я не разбираюсь, увы (:
Спасибо! Теперь прояснилось (:
Спасибо! Теперь прояснилось (:
Это объясняет, почему аналоговый вход идет в обход триггера Шмитта.
И еще вопрос.
Какое максимальное напряжение можно пропустить через полевик в Open-drain режиме?
vdd+4v если не ошибаюсь
Статьи отличные! Очень ясное изложение. Спасибо! Жаль нет продолжения.
Только про Open-drain похоже не совсем верно.Этот режим работает и без доп. транзистора.
Просто выход подтягивается резистором к 5в. В этом режиме при записи 0 выход притягивается к земле, а при записи 1 отпускается (отключается), и напряжение на выходе 5в. А ещё этот режим позволяет объединять выходы по «И» (1w, i2c так работают).
Ребят, только изучаю АРМ, подскажите кто знает максимальную скорость переключения вывода, у меня она ограничивается в 400 кГц.
Плата STM32 Discovery, вот код:
Делаю замер частоты на выводе SPEED — показывает 200кГц, а так как стоит ^(SPEED) — значт умножаем полученную частоту на 2, получаем 400кГц.
Я проверил — действительно, получается около 400 КГц. Но тут нет ничего странного, ведь нужно учитывать скорость выполнения программы и то, что функции не встраиваются (inline), если выключены оптимизации: каждый вызов функции вносит задержку. Так что у вас есть два варианта:
1. Собирать Release-версию проекта со включенными оптимизациями (-O2 или -Os). Скорость переключения достигнет 1 МГц.
2. Не использовать библиотечные функции, а писать так:
Скорость будет около 2 МГц.
В любом случае, добиться скорости переключения вручную в 10 МГц невозможно. С другой стороны, таймер вполне может выжать все 10 МГц, т.к. у него переключение реализовано аппаратно.
Да, спасибо, через ODR скорость выросла до 1.6мГц, а если еще оптимизировать по О3 — то поднимается до 2.6мГц.
А что за оптимизация Оs?, у меня в программе coocox есть варианты только О1, О2, О3, ну и конечно же NONE,
Это оптимизация размера кода, также включает в себя -O2. Если Linux есть, то смотри man gcc насчёт оптимизаций. Кстати, при использовании -O3 я бы рекомендовал отлаживать и тестировать код особенно тщательно, т.к. этот уровень предполагает использование потенциально небезопасных оптимизаций, которые могут привести к некорректной работе кода.