[Home] [Donate!] [Контакты]

GPIO: порт ввода/вывода общего назначения

GPIO (general-purpose I/O port) - порт ввода/вывода общего назначения, служит для низкоуровневого обмена цифровыми сигналами с внешними по отношению к микроконтроллеру устройствами. Позволяет программно установить состояние выхода (вывести на выход логические 0 или 1) и программно прочитать состояние вывода микроконтроллера (определить логический уровень сигнала на выводе в момент чтения). Причём считывать можем как состояние входа, так и выхода.

В то время как чтение входа - вполне очевидное действие, необходимость в чтении выхода требует пояснений. Считывать состояние выхода бывает полезно в различных ситуациях. Например, в режиме открытого стока, сигнал на выводе зависит не только от состояния данного выхода (несколько выходов с открытым стоком могут быть соединены вместе, образуя так называемое "монтажное И" или "монтажное ИЛИ"). А также возможно считывание выхода в диагностических целях или в целях отладки (допустим, возможна простая программная реализация логического анализатора для контроля за работой собственных периферийных устройств).

Далее рассмотрим возможности, свойства, особенности, характеристики и основы работы с GPIO. Будет рассмотрен также базовый пример использования GPIO.

Оглавление
GPIO: порт ввода/вывода общего назначения
Введение
GPIO: обзор
GPIO: устройство
GPIO: характеристики
GPIO: управление
Пример программы
Неиспользуемые выводы
Защита выводов от перегрузки
Смотрите также
Минимальное приложение C++ для микроконтроллера ARM (Cortex-M3)
Исключения и прерывания
SysTick: Системный таймер
EXTI: контроллер внешних прерываний/событий

Введение

Большинство выводов микроконтроллера могут использоваться как GPIO выводы. Мы имеем возможность программно настроить и использовать каждый из выводов, работая с ними индивидуально или управляя сразу целой группой выводов. Прежде всего - настроить вывод как выход или вход, а также выбрать нужный режим работы, а затем, в любой момент времени программно установить требуемый логический уровень на выходе или прочитать состояние любого вывода.

В качестве базового примера, приведём простейшую программу, в которой осуществляется работа с GPIO. Здесь используется оценочная плата STM32VLDISCOVERY с микроконтроллером STM32F100RBT6B (подробнее об используемых средствах разработки смотрите "Минимальное приложение C++ для микроконтроллера ARM". )

// (!) VECT_TAB_SRAM - определить символ для отладки в RAM (!)

#include <stm32f10x.h>

int main()
{
    // Включаем тактовый сигнал порта GPIOC.
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;

    // Конфигурируем PC9 как двухтактный выход, основная функция,
    // макс. частота 2МГц (CNF9: 00; MODE9: 10).
    // Остальные выводы PCx - аналоговые входы (CNFx: 00, MODEx: 00).
    GPIOC->CRH = GPIO_CRH_MODE9_1;
    
    // Включить LED.
    // Используется регистр установки/сброса битов BSRR.
    GPIOC->BSRR = GPIO_BSRR_BS9;
    // Если бы не CMSIS, пришлось бы писать что-то вроде:
    // *(uint32_t*)(0x40011000+0x10) = 1 << 9;

    // Выключить LED.
    // Сброс бита с помощь регистра установки/сброса битов BSRR
    // (также сброс возможен с помощью BRR регистра).
    GPIOC->BSRR = GPIO_BSRR_BR9;
    
    // Включить LED.
    // Чтение/модификация/запись регистра выходных данных; неатомарно и
    // небезопасно, если доступ к одному ресурсу происходит как из
    // основной программы, так и из обработчика прерывания.
    GPIOC->ODR |= GPIO_ODR_ODR9;

    // Стоп (бесконечный цикл).
    while(true) {}
}
// Подробнее - далее...

На указанной оценочной плате установлены два светодиода, подключённые через токозадающие резисторы  к выводам микроконтроллера. Зелёный подключён к выводу PC9, синий - к PC8. Зажигаются высоким уровнем на соответствующем выводе микроконтроллера (логическая 1). В предложенной программе происходит управление зелёным светодиодом, который в данном случае является индикатором состояния выхода, к которому он подключён.

Прежде чем перейти к анализу программы, рассмотрим некоторые теоретические вопросы, касающиеся GPIO.

GPIO: обзор

Микроконтроллер STM32F100xx имеет несколько портов ввода/вывода общего назначения, обозначаемых буквами A, B, и т.д.: (GPIOA, GPIOB, ...). Количество портов зависит от модели микроконтроллера. Каждый порт содержит 16 разрядов (или битов), связанных с соответствующими им выводами микроконтроллера. Разряды нумеруются от 0 до 15. Вывод порта кратко обозначаются PXn (X - буква порта, n - номер разряда, например, уже упоминавшиеся PC8 или PC9). Каждый разряд может быть индивидуально программно сконфигурирован как вход или выход.

Предусмотрены 4 варианта режима для входа, и 4 режима для выхода.

Вход может быть:
floating - "плавающий", т.е. цифровой вход без подтягивающих резисторов, а значит, с высоким входным сопротивлением;
pull-up - цифровой вход с внутренним, подтягивающим к высокому уровню резистором;
pull-down - цифровой вход с подтягивающим к низкому уровню резистором;
analog - аналоговый вход.

Выход может быть:
push-pull - двухтактный для выполнения основной функции (обычно основная функция - это вывод GPIO);
open-drain - с открытым стоком для выполнения основной функции;
alternate function push-pull - двухтактный для выполнения альтернативной функции;
alternate function open-drain - с открытым стоком для выполнения альтернативной функции.

Для каждого из указанных выходных режимов предусмотрен выбор одного из трёх скоростных вариантов.

Во время и после сброса все выводы портов сконфигурированы как плавающие входы.

Так как количество встроенных в микроконтроллер периферийных устройств велико, а количество выводов ограничено, то на многие выводы оказывается возложено несколько функций - основная и одна или несколько альтернативных. Основная функция для большинства выводов - работа в качестве вывода GPIO. Альтернативная функция - использование вывода как выхода периферийного устройства микроконтроллера, привязанного к этому выводу. Чтобы вывод начал работать как выход периферийного устройства, при настройке вывода следует выбрать режим выхода для выполнения альтернативной функции. При использовании вывода как входа, настраиваем его просто как вход (цифровой вход с подтяжкой или без неё). Специального конфигурирования для входа периферийного устройства не требуется - достаточно лишь активировать само периферийное устройство - чтение, в отличие от записи, может бесконфликтно осуществляться одновременно несколькими устройствами.

Исключение - входы ADC, для использования они должны быть настроены как аналоговые входы. Ещё одно исключение - выходы DAC, которые тоже, как ни странно, должны быть настроены как аналоговые входы.

Например, у микроконтроллеров STM32F100xx вывод с основной функцией PA0 имеет альтернативные функции: WKUP (сигнал для пробуждения из режима ожидания standby), USART2_CTS (одна из линий второго интерфейса USART), ADC1_IN0 (вход 0 ADC, аналоговый), TIM2_CH1_ETR (внешний тактовый сигнал для второго таймера). Разумеется, во избежание конфликтов, одновременно может быть включено только одно из периферийных устройств, использующих общий выход.

Среди микроконтроллеров STM32F100xx есть варианты в корпусах LQFP100, LQFP64, TFBGA64, LQFP48 (LQFP - выводы с каждой стороны квадратного корпуса, TFBGA - матрица жёстких шариковых выводов снизу). В зависимости от исполнения доступно разное количество портов ввода/вывода. В 100-выводном корпусе доступны A, B, C, D, E порты (16*5 - всего 80 бит); в 64-выводном - A, B, C (вывод PC3 доступен в LQFP64, но недоступен в TFBGA64) и частично D (PD0, PD1, PD2); в 48-выводном корпусе доступны только A, B, частично C (PC13, PC14, PC15), частично D (PD0, PD1).

Есть определённые ограничения на использование некоторых портов ввода/вывода. Выводы PA13, PA14, PA15, PB3, PB4 после сброса конфигурируются как используемые для отладки, что существенно ограничивает возможность их применения как портов ввода/вывода. PD0, PD1 используются для подключения кварцевого резонатора (это основная функция). К PC14, PC15 подключается кварц часов реального времени на 32 кГц (альтернативная, но часто используемая функция). PB2 является по совместительству входом BOOT1, совместно с BOOT0 определяющим способ начальной загрузки (правда, если на BOOT0 подан 0, задающий режим загрузки из Flash, а это самый часто используемый вариант, то сигнал на входе BOOT1 безразличен и PB2 можно использовать свободно). Кроме того, PC13, PC14 и PC15 получают питание через ключ с малым допустимым током (около 3 мА), так что использование PC13..PC15 в режиме выхода весьма ограничено: частота переключения не должна превышать 2 МГц, максимальная ёмкость нагрузки 30 пФ, недопустимо использовать выход в качестве источника тока (например, для подключения светодиодов).

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

GPIO: устройство

Универсальность и множество доступных режимов работы вывода GPIO обеспечивается благодаря достаточно сложной схеме управления выводом. На рисунке изображена структурная схема для одного разряда порта ввода/вывода.

Структура GPIO.
Рис. %img:gpio

Вывод микросхемы имеет традиционную защиту от статического электричества, реализованную с помощью двух диодов. Оба защитных диода закрыты, пока напряжение на входе остаётся в пределах -ΔV..VDD+ΔV (где ΔV - напряжение, при котором открывается диод). При выходе напряжения за указанные пределы один из диодов открывается. Протекающий через диод ток вызывает падение напряжения на внутреннем сопротивлении источника сигнала и происходит ограничение сигнала. Или же, в случае статического электричества, в зависимости от знака, статический заряд разрядится через один из диодов, что защитит от повреждения подключённые к выводу цепи микроконтроллера. Ток через защитные диоды (инжектированный ток), а также суммарный инжектированный ток не должны превышать указанных в Datasheet пределов (для STM32F100xx: ±5 мА, за положительный инжектированный ток принимается втекающий в вывод микроконтроллера ток; кроме PA5, PA6, PA7, для которых установлено +5 мА/-0 мА, т.е. не допускается вытекающий инжектированный ток, что связано с влиянием на точность ADC; суммарный по всем выводам максимальный инжектированный ток: ±25 мА).

Наличие защиты вызывает известные проблемы согласования уровней сигналов при работе микроконтроллера совместно с логикой, питаемой напряжением 5 В, которая всё ещё очень распространена. Так как напряжение питания микроконтроллера VDD = 2..3.6 В, то если подать на вход 5 В, откроется верхний по схеме диод защиты. Чтобы решить проблему, разработчики микроконтроллера предусмотрели так называемые выводы с допустимым напряжением 5 В (FT - five-volt tolerant, 5-вольт "толерантный"). Верхний по схеме защитный диод этих выводов подключён для обратного смещения не к источнику питания с напряжением VDD, а к точке схемы с потенциалом величиной VDD_FT = 5 В. Хитрость в том, что источника 5 В, естественно, в микроконтроллере нет. Этот потенциал формируется за счёт питания от самих FT-выводов и ограничения его уровня значением 5 В (и если ни на одном FT-выводе нет пяти вольт, то и смещающее напряжение не достигнет пяти вольт). Потребляемый от вывода ток очень мал, но он подвержен трудно предсказуемым изменениям в зависимости от соотношения величин напряжения на других FT-выводах. Это приводит к появлению шума, уровень которого несущественен для цифровых компонентов, но недопустим для аналоговых сигналов. Поэтому выводы, которые используются для работы с аналоговыми сигналами, выполнены как не FT-выводы.

Чтобы получить на выходе сигнал с уровнем логической единицы до 5 В, следует использовать FT-вывод микроконтроллера в режиме выхода с открытым стоком (разумеется, со смещением от внешнего источника 5 В через резистор).

В datasheet на устройство указывается, какие выводы имеют допустимое напряжение 5 В. У микроконтроллеров STM32F100xx все сигнальные выводы являются FT-выводами, кроме: NRST, BOOT0, PA0..PA7, PB0, PB1, PB5, PC0..PC5, PC13, PC14, PC15 (из них PA0..PA7, PB0, PB1, PC0..PC5 не являются FT по той причине, что они в качестве альтернативной функции являются аналоговыми входами ADC: входы ADC1_IN0..ADC1_IN15). Уточняйте информацию в datasheet на своё устройство.

К выводу подключены драйвер входа и драйвер выхода.

Драйвер выхода выполнен на двух транзисторах: одном p-канальном МОП и одном n-канальном МОП-транзисторе, подключённых к схеме управления. В зависимости от режима, оба они могут быть закрыты (в этом случае драйвер выхода отключён, вывод микросхемы работает как вход); может быть закрыт верхний на схеме P-MOS, а нижний задействован в работе по схеме с открытым стоком или могут быть задействованы оба, образуя двухтактный выход (один открыт, другой закрыт; если открыт верхний по схеме - на выходе получаем высокий уровень - логическую 1, если открыт нижний - на выходе будет низкий уровень, логический 0).

Источником сигнала для драйвера выхода является либо регистр выходных данных, либо сигнал с какого-то периферийного устройства микроконтроллера (если выбран режим альтернативной функции). Регистр выходных данных GPIOx_ODR (x = A, B, ... - буквенное обозначение регистра) программно доступен как для записи, так и для чтения. На выводе микроконтроллера в режиме выхода будет формироваться логический уровень, записанный в соответствующий бит этого регистра. GPIOx_ODR может быть также модифицирован с помощью регистров установки/сброса битов - регистров GPIOx_BSRR, GPIOx_BRR. Эти регистры доступны только для записи. Если при записи в младшую половину регистра установки/сброса битов GPIOx_BSRR, в какие-то разряды происходит запись единичных битов, то соответствующие разряды в регистре выходных данных GPIOx_ODR устанавливаются в 1, остальные разряды остаются без изменения. Аналогично, при записи значения в регистр сброса битов GPIOx_BRR (или в старшую половину GPIOx_BSRR, которая тоже отвечает за сброс), для тех разрядов, в которые записывается 1, соответствующие разряды в регистре выходных данных GPIOx_ODR сбрасываются в 0, остальные не изменяются.

Регистры установки/сброса битов упрощают доступ к отдельным разрядам порта ввода/вывода и обеспечивают атомарность доступа. Под атомарностью имеется в виду неделимость, непрерываемость операции, выполнение операции как одного действия.

Допустим, если требуется изменить логический уровень всего на одном выходе микроконтроллера, то, не пользуясь регистрами установки/сброса битов, пришлось бы выполнить несколько действий: считать значение соответствующего регистра GPIOx_ODR (требуется, так как мы хотим изменить один бит, а остальные оставить в прежнем состоянии); модифицировать изменяемый бит в считанном значении; записать результат обратно в GPIOx_ODR. Проблема не только в том, что такое решение требует большего объёма кода, а его выполнение - времени. Ещё хуже то, что в любой момент между чтением GPIOx_ODR и записью в него нового значения, может произойти прерывание. И если обработчик прерывания обращается к регистрам этого же порта GPIO, а именно, модифицирует содержимое регистра GPIOx_ODR порта, то после возврата из прерывания, ничего не подозревающая программа перезапишет туда же своё значение. Изменения, сделанные внутри прерывания, будут потеряны, алгоритм работы устройства нарушится. Чтобы избежать такой ситуации, придётся запрещать прерывания на время чтения/модификации регистра (если известно, что обработчик какого-либо прерывания может изменить регистр, с которым мы работаем). Это ещё больше увеличит размер кода и отрицательно скажется на быстродействии. Или придётся отказаться от модификации регистра в обработчиках прерываний, что не всегда приемлемо. В то же время регистры GPIOx_BSRR, GPIOx_BRR дают возможность очень просто и быстро выполнить атомарную модификацию регистра выхода.

Драйвер входа содержит в себе схему управления подтягивающими резисторами и триггер Шмитта. Если вывод используется периферийными устройствами с аналоговым входом (например, такими как ADC - аналогово-цифровой преобразователь), то оно подключается непосредственно ко входу. Периферийные устройства с цифровым входом и регистр входных данных подключаются ко входу через триггер Шмитта. Триггер Шмитта на входе - типичное решение для микроконтроллеров, он определяет входные уровни переключения, формирует на своём выходе стандартные уровни цифрового сигнала с хорошими фронтами при переключении независимо от качества входного сигнала. За счёт наличия гистерезиса, снижает чувствительность входа к шуму. Также триггер Шмитта - простое средство для согласование цифровой части схемы с аналоговой без использования дополнительных элементов (однобитовый ADC). Триггер Шмитта включён во всех режимах, кроме режима аналогового входа. В режиме аналогового входа триггер Шмитта отключается и на его выходе формируется логический 0 независимо от сигнала на входе. Регистр входных данных, подключённый к выходу триггера Шмитта, доступен только для чтения. Он может быть прочитан независимо от режима работы соответствующего вывода и содержит значение, соответствующее реальному сигналу на выводе микроконтроллера (кроме режима аналогового входа). Таким образом, можно считывать данные с вывода микроконтроллера даже тогда, когда он работает как выход. Это имеет смысл, так как не всегда это значение будет совпадать с тем, что мы выводим, записывая значение в регистр вывода. Например, если используется выход с открытым стоком, то сигнал на выходе будет определяться также и другими, подключёнными к этой линии выходами (которые образуют "монтажное" И или ИЛИ). Если вывод сконфигурирован как аналоговый вход, то при чтении из регистра в соответствующем разряде будем получать 0 (триггер Шмитта отключён).

Выбором соответствующего режима входа, может быть подключен один из подтягивающих резисторов. Наличие встроенных подтягивающих резисторов позволяет сократить количество компонентов на плате и упростить устройство в целом. Резисторы используются для того, чтобы принудительно задать уровень сигнала (высокий или низкий) на входе, который может в какие-то моменты работы устройства оставаться неподключённым, "висящим". Пример: вывод подключён через кнопку к общему проводу. Когда кнопка нажата, на выводе формируется логический 0. При разомкнутой кнопке подтягивающий резистор pull-up задаст логическую 1. Другие возможные варианты использования: подтягивание неиспользуемых выводов микроконтроллера (чтобы избежать наведения помех в этих выводах); при подключении к выходу с Z-состоянием или к выходу с открытым стоком. Стоит иметь в виду, что сопротивление внутренних подтягивающих резисторов довольно велико (для STM32F100xx по datasheet находится в пределах 30..50 кОм, типичное значение 40 кОм). С другой стороны, например для механических кнопок, может быть ограничен минимальный ток. При токе ниже минимального не гарантируется низкое сопротивление замкнутых контактов нажатой кнопки, кроме того это сопротивление может быть нестабильным и являться источником повышенного шума. Не знаю, по этой причине или из других соображений, но в STM32 value line Discovery evaluation board, например, используются внешние подтягивающие резисторы для подключения кнопок.

Кроме того могут быть и нетрадиционные варианты использования режима. Например, вход с подтягивающим резистором можно использовать как выход с большим внутренним сопротивлением! Такой вариант тоже иногда бывает полезен, особенно при желании минимизировать количество внешних компонентов.

GPIO: характеристики

Здесь будут рассмотрены только некоторые характеристики портов ввода/вывода микроконтроллеров STM32F100xx. За подробной информацией обращайтесь к datasheet.

Примечание. В datasheet указываются минимальные, максимальные и типовые значения величин. Если не указано иное, то гарантируется, что значение не выходит за пределы минимума и максимума в наиболее неблагоприятных условиях температуры окружающей среды, напряжения питания, тактовой частоты во всех испытуемых устройствах. Тестирование производится при температуре окружающей среды 25º C и при максимальной температуре (85/105º C при максимальной рассеиваемой мощности или 105/125º C при малой, в зависимости от варианта исполнения - соответственно для устройств с суффиксами 6 и 7).

Типовые значения обычно даются для температуры окружающей среды 25º C, напряжения питания 3.3 В.

Максимальные значения тока
Обозначение Характеристика Max Единица
IVDD Общий ток питания по линиям VDD и VDDA 150 мА
IVSS Общий ток по линиям земли VSS 150
IIO Ток выхода, втекающий в любой порт I/O или вывод управления (вывод - потребитель) +25
То же для вытекающего тока (вывод - источник) -25
IINJ(PIN) Инжектированный ток (через защитный диод) для выводов PA5, PA6, PA7 +5/-0
Инжектированный ток для всех остальных выводов (за положительный принят втекающий ток, который возникает при VIN>VDD) ±5
ΣIINJ(PIN) Общий инжектированный ток (сумма IINJ(PIN) для всех выводов) ±25

Максимально допустимый выходной ток одного вывода составляет 25 мА; если нагружены несколько выводов, то нужно учитывать ограничение по току линий питания микроконтроллера и линий земли величиной 150 мА. Например, если 6 выводов отдают ток по 25 мА каждый, потребуется 150 мА от источника по линиям VDD. Так как сам микроконтроллер тоже потребляет ток, то уже получаем перегрузку.

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

Уровни напряжений на выходе
Обозначение Параметр Условия измерения Min Max Единица
VOL Напряжение низкого уровня на выходе IIO=6 мА
2 В<VDD<2.7 В
  0.4 В
VOH Напряжение высокого уровня на выходе VDD-0.4  
VOL Напряжение низкого уровня на выходе IIO=8 мА
2.7 В<VDD<3.6 В
  0.4
VOH Напряжение высокого уровня на выходе VDD-0.4  
VOL Напряжение низкого уровня на выходе IIO=20 мА
2.7 В<VDD<3.6 В
  1.3
VOH Напряжение высокого уровня на выходе VDD-1.3  

Выходы микроконтроллера совместимы с TLL и CMOS уровнями сигналов. Напряжение сигнала низкого уровня на выходе TTL должно составлять не более 0.4 В, высокого - не менее 2.4 В. При этом TTL вход должен воспринимать напряжение до 0.8 В как низкий уровень, а выше 2 В - как высокий (разница между уровнями для входа и выхода обеспечивает помехозащищённость). В отличие от TTL, для CMOS уровней нет фиксированных значений, они зависят от напряжения питания. Для входа ориентировочно принимают 0.35*VDD (низкий уровень) и 0.65*VDD (высокий уровень), при напряжении питания 3.3 В соответственно 0.66 В и 2.64 В. Выход CMOS должен обеспечить указанный уровень с запасом для обеспечения желаемой помехозащищённости.

Однако совместимость наблюдается только при не слишком больших токах выхода: до 6 мА при низких напряжениях питания (до 2.7 В) и до 8 мА при напряжении питания свыше 2.7 В. Большие токи допустимы, если только нет строгих требований к уровням выходного напряжения (например, управление ключом на биполярном транзисторе, управление оптоэлектронными приборами).

Характеристики входа
Обозначение Параметр Условия измерения Min Typ Max Единица
VIL Напряжение низкого уровня стандартного входа   -0.5   0.28*(VVDD-2)+0.8 В
Напряжение низкого уровня FT-входа -0.5   0.32*(VVDD-2)+0.75
VIH Напряжение высокого уровня стандартного входа 0.41*(VDD–2)+1.3   VDD+0.5
Напряжение высокого уровня FT-входа 0.42*(VDD–2)+1   5.5
Vhys Гистерезис триггера Шмитта на стандартном входе   200     мВ
Гистерезис триггера Шмитта на FT-входе 5% VDD      
Ilkg Ток утечки входа VSS≤VIN≤VDD
Стандартный вход
    ±1 мкА
VIN=5 В
FT-вход
    3
RPU Сопротивление подтягивающего к высокому уровню резистора VIN=VSS 30 40 50 кОм
RPD Сопротивление подтягивающего к низкому уровню резистора VIN=VDD
CIO Ёмкость входа     5   пФ

Входы также являются TTL и CMOS-совместимыми. Микроконтроллер без проблем может работать в смешанных схемах, содержащих CMOS и TTL-логику с напряжением питания 5 В. Неплохо, особенно с учётом того, что сами микросхемы TTL и CMOS не всегда совместимы между собой без преобразования уровней!

GPIO: управление

GPIO, как и все остальные устройства микроконтроллера, управляются с помощью регистров. Полный перечень регистров, их адреса, назначение, содержимое после сброса, правила доступа можно найти в руководстве пользователя для микроконтроллера.

Очень важно не забывать включать тактовый сигнал перед использованием устройства! Тактовый сигнал любого периферийного устройства включается и отключается индивидуально. Это позволяет оптимизировать энергопотребление микроконтроллера в целом, так как есть возможность включить тактовый сигнал только для используемых устройств. В STM32F100xx есть две APB шины для подключения периферии. К APB1 подключены низкоскоростные периферийные устройства, а к APB2 - высокоскоростные (но по умолчанию стартовый код для обеих шин устанавливает одинаковую тактовую частоту, 24 МГц). Порты ввода/вывода подключены к APB2, для включения/отключения их тактового сигнала используется регистр APB2ENR устройства RCC (Reset and clock control). Например, чтобы включить порт GPIOC, нужно установить бит IOPCEN регистра. С использованием CMSIS код для включения тактирования GPIOC будет иметь вид:

RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
Регистры GPIO
GPIOx_CRL Регистры конфигурации порта. Для конфигурирования каждого разряда порта используется 4 бита. Порт содержит 16 разрядов, поэтому всего нужно 64 бита для конфигурирования порта, т.е. два слова: GPIOx_CRL для младшей половины порта (разряды 0..7) и GPIOx_CRH для старшей (8..15).
GPIOx_CRH
GPIOx_IDR Регистр входных данных порта, только для чтения. 16 младших разрядов регистра содержат логический уровень сигнала на соответствующем выводе порта.
GPIOx_ODR Регистр выходных данных. Определяет логический уровень, формируемый на соответствующих выводах порта, работающих в режиме выхода. Доступен для записи и для чтения. Изменить содержимое регистра можно также с помощью регистров GPIOx_BSRR, GPIOx_BRR.
GPIOx_BSRR Регистр установки/сброса битов в GPIOx_ODR. Только для записи. Младшие 16 разрядов служат для установки битов в регистре выходных данных, а старшие - для сброса, установка имеет приоритет над сбросом. Запись 1 в младшие 16 разрядов регистра приводит к установке соответствующих разрядов в регистре GPIOx_ODR, запись нулей не изменяет соответствующие разряды. Запись 1 в разряды 16..31 регистра GPIOx_BSRR приводит к сбросу соответствующих им разрядов 0..15 в GPIOx_BSRR.
GPIOx_BRR Регистр сброса битов в GPIOx_ODR. Только для записи. Запись 1 в разряды регистра сбрасывает соответствующие разряды в регистре GPIOx_ODR, запись 0 не изменяет соответствующий разряд.
GPIOx_LCKR Регистр блокировки порта, запись определённой последовательности в порт защищает выбранные биты от дальнейшей модификации. Чтение остаётся доступным.
AFIO_MAPR Регистры для управления переназначением альтернативных функций: некоторые альтернативные функции, закреплённые по умолчанию за одним выводом микроконтроллера, могут быть переназначены на другой вывод. Возможность используется, если требуется освободить какой-то вывод, для выполнения другой функции (бывает необходимо при одновременной работе периферийных устройств микроконтроллера, по умолчанию использующих один и тот же вывод).
AFIO_MAPR2
AFIO_EXTICR1 Выбор входа - источника сигнала для генерации внешнего прерывания.
AFIO_EXTICR2
AFIO_EXTICR3
AFIO_EXTICR4

Регистры конфигурации порта GPIOx_CRL, GPIOx_CRH определяют режим работы каждого вывода, которому соответствует свой разряд порта. На каждый разряд отведено 4 конфигурационных бита: для разрядов 0..7 - в регистре GPIOx_CRL (биты 0..3 конфигурируют разряд 0, биты 4..7 - разряд 1 и т.д.), для разрядов 8..15 - в регистре GPIOx_CRH (биты 0..3 - конфигурируют разряд 8 порта, биты 4..7 - разряд 9 и т.д.).

Таким образом, регистр GPIOx_CRL содержит 8 битовых полей размером по 4 бита каждое, отвечающих за конфигурирование соответствующих разрядов 0..7 порта GPIOx, структура регистра поясняется следующей таблицей:

GPIOx_CRL (Port configuration register low)
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16  
Px7 conf. Px6 conf. Px5 conf. Px4 conf.  
rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw  
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0  
Px3 conf. Px2 conf. Px1 conf. Px0 conf.
rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw  

Структура регистров GPIOx_CRH аналогична:

GPIOx_CRH (Port configuration register high)
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16  
Px15 conf. Px14 conf. Px13 conf. Px12 conf.  
rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw  
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0  
Px11 conf. Px10 conf. Px9 conf. Px8 conf.
rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw  

Теперь перейдём к рассмотрению самого битового поля, определяющего режим работы соответствующего ему разряда порта (соответствующего вывода микроконтроллера).

Порядок следования битов в битовом поле конфигурации разряда
Смещение бита +3 +2 +1 +0
Наименование CNF1 CNF0 MODE1 MODE0

Биты MODE (два младших бита четырёхбитового поля конфигурации) задают режим работы вывода: вход или выход, а для выхода, кроме того, определяют скоростные возможности.

Значение битов MODE
MODE Режим
00 Вход
01 Выход, максимальная частота 10 МГц
10 Выход, максимальная частота 2 МГц
11 Выход, максимальная частота 50 МГц

Такой загадочный параметр, как максимальная частота выходного сигнала всего лишь определяет время нарастания и спада сигнала на выходе при переключении. Величина 50 МГц указанная в руководстве (Reference manual) проникла туда, очевидно из документации на более высокоскоростные устройства, тогда как в спецификации (Datasheet) честно говорится о максимальных частотах 10, 2 и 24 МГц. Время спада и нарастания при переключении составляет 1/4 часть от периода максимальной частоты при ёмкости нагрузки 50 пФ (t=1/(4*Fmax)). Составляет соответственно 25 нс, 125 нс и 8..12 нс. Так как увеличение скорости переключения приводит к увеличению потребляемой мощности, не стоит без необходимости выбирать высокоскоростные режимы.

Биты CNF определяют детали конфигурации для бита порта в каждом из режимов.

Значение битов CNF в режиме входа
CNF Значение
00 Аналоговый вход
01 Цифровой плавающий вход
10 Цифровой вход с подтягивающим резистором
11 Зарезервировано

Если вход сконфигурирован для работы с подтягивающим резистором, то какой из двух подтягивающих резисторов будет использоваться, определяется регистром выходных данных GPIOx_ODR. Если соответствующий выводу бит регистра содержит 0, включается резистор, подтягивающий к низкому уровню, если 1 - к высокому.

Во время и после сброса, выводы портов вводы/выводов конфигурируются как плавающие входы (во избежание конфликтов с подключёнными к микроконтроллеру устройствами).

Варианты режимов выхода задаются битами CNF и приведены в следующей таблице.

Значение битов CNF в режиме выхода
CNF Значение
00 Двухтактный выход
01 Выход с открытым стоком
10 Альтернативная функция, двухтактный выход
11 Альтернативная функция, выход с открытым стоком

Как мы видим, конфигурацию вывода определяют 4 бита, т.е. одна 16-ричная цифра. Это довольно удобно с точки зрения программирования. В присваивании вида GPIOx->CRL = 0xXXXXXXXX или GPIOx->CRH = 0xXXXXXXXX каждая цифра конфигурирует свой разряд (не забываем, что в записи числа младшая цифра находится справа, соответственно слева - старшая цифра).

Конфигурационное значение для разряда определяем путём комбинирования отдельных битов в соответствии с требуемым режимом работы вывода. Или можем сразу взять готовое значение из таблицы.

Режим работы вывода Конфигурационное значение
Вход аналоговый 0x0
Вход цифровой плавающий 0x4
Вход цифровой с подтяжкой 0x8
Выход, основная функция, двухтактный, 10МГц 0x1
Выход, основная функция, двухтактный, 2МГц 0x2
Выход, основная функция, двухтактный, 50МГц 0x3
Выход, основная функция, открытый сток, 10МГц 0x5
Выход, основная функция, открытый сток, 2МГц 0x6
Выход, основная функция, открытый сток, 50МГц 0x7
Выход, альтернативная функция, двухтактный, 10МГц 0x9
Выход, альтернативная функция, двухтактный, 2МГц 0xA
Выход, альтернативная функция, двухтактный, 50МГц 0xB
Выход, альтернативная функция, открытый сток, 10МГц 0xD
Выход, альтернативная функция, открытый сток, 2МГц 0xE
Выход, альтернативная функция, открытый сток, 50МГц 0xF

В следующей таблице приводится обратное соответствие: конфигурационное значение - режим работы (для более удобного поиска по значению).

Конфигурационное значение Режим работы вывода
0x0 Вход аналоговый
0x1 Выход, основная функция, двухтактный, 10МГц
0x2 Выход, основная функция, двухтактный, 2МГц
0x3 Выход, основная функция, двухтактный, 50МГц
0x4 Вход цифровой плавающий
0x5 Выход, основная функция, открытый сток, 10МГц
0x6 Выход, основная функция, открытый сток, 2МГц
0x7 Выход, основная функция, открытый сток, 50МГц
0x8 Вход цифровой с подтяжкой
0x9 Выход, альтернативная функция, двухтактный, 10МГц
0xA Выход, альтернативная функция, двухтактный, 2МГц
0xB Выход, альтернативная функция, двухтактный, 50МГц
0xC X
0xD Выход, альтернативная функция, открытый сток, 10МГц
0xE Выход, альтернативная функция, открытый сток, 2МГц
0xF Выход, альтернативная функция, открытый сток, 50МГц

Тогда, например, если мы хотим, чтобы вывод PC0 был двухтактным низкоскоростным (до 2 МГц) выходом, выполняющим основную функцию, а выводы PC1..PC7 - цифровыми плавающими входами, выполняем

GPIOC->CRL = 0x44444442;

Если хотим, чтобы PC9 был высокоскоростным двухтактным выходом с основной функцией, а выводы PC8, PC10..PC15 - аналоговыми входами, выполняем

GPIOC->CRH = 0x00000030;

Отметим, что при сбросе в конфигурационные регистры портов ввода вывода загружаются значения по умолчанию 0x44444444 (выводы - цифровые плавающие входы).

Если мы хотим изменить конфигурацию только одного или нескольких выводов, не трогая остальных, делаем это как обычно, в два этапа: сначала сбрасываем все биты изменяемых битовых полей (оператором &), а затем устанавливаем новое значение полей (оператором | или + ). При этом есть смысл воспользоваться дополнительной переменной для вычислений. Настроим PC9 как в предыдущем примере, при этом настройку остальных выводов не будем изменять:

uint32_t c = GPIOC->CRH;
c &= ~ 0x000000F0;
// Или:    c &= 0xFFFFFF0F;    - кому как больше нравится.
c |= 0x00000030;
GPIOC->CRH = c;

Существует более короткий в записи вариант без промежуточной переменной, но его в данном случае лучше не использовать:

// Так лучше не делать:
(GPIOC->CRH &= ~ 0x000000F0) |= 0x00000030;

Причина - в процессе вычислений, прежде чем в регистр будет помещено окончательное значение, в него попадёт результат промежуточных вычислений (со сброшенным модифицируемым битовым полем, что соответствует режиму аналогового входа для этого вывода). В некоторых случаях это может привести к нежелательным побочным эффектам.

А так можно, это полностью эквивалентно варианту с промежуточной переменной:

GPIOC->CRH = GPIOC->CRH & ~ 0x000000F0 | 0x00000030;

Пример программы

Итак, обещанная программа для зажигания светодиода, подключенного анодом через резистор к PC9.

// (!) VECT_TAB_SRAM - определить символ для отладки в RAM (!)

#include <stm32f10x.h>

int main()
{
    // Включаем тактовый сигнал порта GPIOC.
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;

    // Конфигурируем PC9 как двухтактный выход,
    // основная функция, макс. частота 2МГц.
    GPIOC->CRH =
            GPIO_CRH_CNF9_1&0 |    //CNF: 00
            GPIO_CRH_CNF9_0&0 |    //(push-pull)
            GPIO_CRH_MODE9_1 |     //MODE: 10
            GPIO_CRH_MODE9_0&0;   //(output, 2MHz)
    // Это то же самое, что
    // GPIOC->CRH = GPIO_CRH_MODE9_1;
    // просто я хотел упомянуть все биты для конфигурирования PC9.
    // Выводы PC8, PC10..PC15 при этом конфигурируются как аналоговые
    // входы (CNFx: 00, MODEx: 00 - так как все биты в инициализирующем
    // значении для GPIOC->CRH, кроме бита GPIO_CRH_MODE9_1 - нулевые).

    // То же самое можно было записать намного проще!
    // GPIOC->CRH = 0x00000020;

    // Включить LED.
    // Так осуществляется доступ к регистрам без использования CMSIS.
    *(uint32_t*)(0x40011000+0x10) = 1 << 9;
    
    // С использованием CMSIS эта строка будет иметь вид:
    // GPIOC->BSRR = GPIO_BSRR_BS9;


    // Выключить LED.
    // Сброс бита с помощь регистра установки/сброса битов.
    GPIOC->BSRR = GPIO_BSRR_BR9;
    
    // Включить LED.
    // Установка бита с помощью регистра установки/сброса битов.
    GPIOC->BSRR = GPIO_BSRR_BS9;
    
    // Выключить - включить LED.
    // Чтение/модификация/запись регистра выходных данных;
    // неатомарно, иногда небезопасно из-за прерываний.
    GPIOC->ODR &= ~ GPIO_ODR_ODR9;    //Led off.
    GPIOC->ODR |= GPIO_ODR_ODR9;      //Led on.
    
    // Выключить тактирование GPIOC. Порт остаётся в прежнем состоянии,
    // светодиод горит.
    RCC->APB2ENR &= ~ RCC_APB2ENR_IOPCEN;

    // Когда тактовый сигнал отключён,
    // запись в регистр устройства не даёт эффекта.
    // Пытаемся сбросить бит с помощью регистра сброса битов, но безуспешно.
    GPIOC->BSRR = GPIO_BSRR_BR9;

    // Стоп.
    while(true) {}
}

Выполняя программу по шагам с помощью отладчика, можно наблюдать за поведением светодиода. В данном случае он является индикатором состояния выхода PC9, которым мы управляем с помощью регистров порта.

Повторим ещё раз два важных момента. Во-первых, для работы устройств микроконтроллера требуется тактовый сигнал. Для каждого из периферийных устройств по отдельности его можно включить или выключить. Сложная система управления тактированием служит для экономии потребляемой энергии - мы можем включить только то, что используем. Для минимизации потребления, после сброса тактовый сигнал для всех периферийных устройств отключён. Так что важно не забыть его включить для тех устройств, с которыми собираемся работать.

Во-вторых, следует учитывать, что не для всех регистров устанавливаемое после сброса значение - нулевое. Например, для регистров конфигурирования портов GPIOx->CRL и GPIOx->CRH после сброса устанавливается значение 0x44444444 (все выводы - плавающие входы). Это значит, что сначала требуется сбросить битовое поле, если новое значение устанавливается оператором |=

// Выполнение следующего кода после сброса переведёт PC9 в режим
// выхода с открытым стоком, а не двухтактного выхода:
// было: CNF9: 01, MODE9: 00 - плавающий вход;
// устанавливаем бит MODE9_1 и получаем: CNF9: 01, MODE9: 10 -
// выход с открытым стоком, 2 МГц.
GPIOC->CRH |= 0x00000020;

// Если хотим изменить конфигурацию только вывода PC9, оставив
// остальные выводы в прежнем состоянии, следует использовать общий
// подход в работе с битовыми полями: сбросить старое значение поля,
// затем установить новое.
GPIOC->CRH =
        GPIOC->CRH & ~ 0x000000F0 |  // Сброс по маске.
        0x00000020;                  // Установка битов.

Неиспользуемые выводы

Разработчики микроконтроллеров стремятся создавать многофункциональные, универсальные, с широкими возможностями, с богатой периферией устройства, которые могли бы найти применение в разных сферах. Но необязательно все заложенные в микроконтроллер возможности будут востребованы в данном конкретном устройстве. И далеко не всегда оказываются задействованы все выводы микроконтроллера. О неиспользуемых выводах следует позаботиться особым образом. "Висящие в воздухе" выводы - это не просто плохой стиль проектирования, это потенциальные проблемы в будущем.

Во время и сразу после сброса микроконтроллера, выводы портов ввода/вывода конфигурируются как плавающие входы - во избежание конфликтов с подключёнными к микроконтроллеру устройствами. Конфликты могут быть следующими. Наиболее неприятный вариант - подключение выхода к выходу, тогда если эти выходы пытаются установить разные уровни, оба будут перегружены по току, что может закончиться выходом из строя обеих сторон конфликта. Другой вариант - вход с внутренним подтягивающим резистором. Он может нарушить режим работы аналоговой части схемы, к которой подключён. С учётом всего этого, старт микроконтроллера с выводами в состоянии высокого сопротивления - очевидно разумное решение.

С другой стороны, обладая высоким входным сопротивлением, неподключённые входы очень подвержены воздействию помех. Диодная защита на входе предотвратит его повреждение от воздействия помех с напряжением выше допустимого значения, но не препятствует переключению логических элементов под действием этих помех, что приводит к росту потребляемой мощности.

Существует несколько вариантов решения проблемы. Например, если сконфигурировать вывод как вход с подтягивающим резистором, то резистор значительно снизит сопротивление входа, делая его менее восприимчивым к помехам. Или можно сконфигурировать вывод как выход. Правда это создаст угрозу возникновения конфликтов в будущем, если устройство будет модифицировано, незадействованные выводы задействованы, а код конфигурирования выводов по ошибке не будет исправлен. В документации от STM предлагается ещё один, менее очевидный способ: конфигурирование неиспользуемых выводов как аналоговых входов - в этом случае триггер Шмитта отключается и на его выходе устанавливается логический 0, т.е. независимо от сигналов на входе, связанные с ним цифровые элементы переключаться не будут.

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

Защита выводов от перегрузки

Для того чтобы ограничить ток через выводы микроконтроллера при ошибочном подключении к внешним элементам схемы, можно использовать включение через ограничивающие ток резисторы. Максимальный ток для большинства выводов микроконтроллеров STM32F100xx составляет 25 мА. Так что резистор сопротивлением 200 Ом защитит вывод от перегрузки как в случае закорачивания линии на землю, так и при случайном подключении линии к источнику питания до 5 В (для FT-выводов) или к источнику VDD (для не FT-выводов). Если принять ёмкость нагрузки равной 50 пФ, то получившаяся RC-цепь будет иметь постоянную времени 10 нс. Поэтому описанная защита не годится для выводов, которые должны работать с высокочастотными сигналами. Если есть угроза случайного подключения не FT-вывода к источнику +5 В, сопротивление резистора должно быть выбрано с учётом максимально допустимого инжекционного тока. Например, при напряжении питания 3.3 В, максимальном инжекционном токе 5 мА, принимая падение напряжения на защитном диоде имеющим порядок 0.5 В, получаем, что требуется резистор с сопротивлением, как минимум 240 Ом.

Защита выводов микроконтроллера от перегрузки.
Рис. %img:prt

На рисунке под I подразумевается сила тока по модулю. Обычно направленный к микросхеме (втекающий) ток считается положительным, а вытекающий ток считается имеющим отрицательное значение.

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

hamper, 2015-12-23 (update: 2020-10-29)
  Рейтинг@Mail.ru