CraftDuino v2.0
  • - это CraftDuino - наш вариант полностью Arduino-совместимой платы.
  • CraftDuino - настоящий конструктор, для очень быстрого прототипирования и реализации идей.
  • Любая возможность автоматизировать что-то с лёгкостью реализуется с CraftDuino!
Просто добавьте CraftDuino!

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 мА вполне годятся в индикаторы.
  • +6
  • 27 декабря 2011, 17:59
  • burjui

Комментарии (16)

RSS свернуть / развернуть
+
0
Хорошая статья (:
Как лучше реализовать работу с сервами? Судя по — robocraft.ru/blog/mechanics/240.html, нужно отмерять задержки с помощью таймера в миллисекундах. Подскажи оптимальный вариант!
avatar

SinauRus

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

burjui

  • 29 декабря 2011, 09:06
+
0
«выставляешь выход в 1 — ток с верхнего резистора идёт на землю»
О каком резисторе идет речь? на рисунке вроде диоды
avatar

mcsa

  • 8 января 2012, 18:15
+
0
Упс, забыл поправить описание после того, как сменил схему. Лучше верну старую.
avatar

burjui

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

Boffin

  • 19 января 2012, 13:23
+
0
Ребят, только изучаю АРМ, подскажите кто знает максимальную скорость переключения вывода, у меня она ограничивается в 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кГц.
avatar

AlexNS

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

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

2. Не использовать библиотечные функции, а писать так:
while(1)
  GPIOA->ODR = GPIOA->ODR ^ SPEED;
Скорость будет около 2 МГц.

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

burjui

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

AlexNS

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

burjui

  • 4 февраля 2012, 19:01
+
+1
Практически, идеальная статья!
К примеру, я не знал для чего режим Open-Drain и как его юзать. Оказывается, это невероятно удобно!
Спасибо тебе, burjui, за то, что нашел время все это описать!

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

solarplexus

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

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

burjui

  • 23 октября 2012, 19:52
+
0
Спасибо! Теперь прояснилось (:
avatar

solarplexus

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

solarplexus

  • 23 октября 2012, 20:08
+
0
И еще вопрос.
Какое максимальное напряжение можно пропустить через полевик в Open-drain режиме?
avatar

solarplexus

  • 23 октября 2012, 19:17
+
0
vdd+4v если не ошибаюсь
avatar

ilvz

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

ilvz

  • 16 ноября 2012, 23:35

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.