STM32 → Порты GPIO

STM32 → Порты GPIO

Помигаем светодиодом!

Поскольку микроконтроллеры STM32 — настоящие 32-битные ARM-ядра, сделать это будет непросто. Здесь всё сильно отличается от привычных методов в PIC или AVR, где было достаточно одной строкой настроить порт на выход, а второй строкой — вывести в него значение — но тем интереснее и гибче.

Архитектура STM32

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

Архитектура контроллера STM32F100 в области GPIO и RCC

Ядро тактируется кварцем, обычно через ФАПЧ. Это — тактовая частота ядра, или SYSCLK. На плате STM32VLDiscovery установлен кварц на 8 МГц, а ФАПЧ в большинстве случаев настраивается как умножитель на 3 — т.е. SYSCLK на плате STM32VLDiscovery обычно равен 24 МГц.

От ядра отходит шина AHB, имеющая свою тактовую частоту — ей можно установить некий прескалер относительно SYSCLK, однако можно оставить его равным единице. Эта шина подобна шине между процессором и северным мостом компьютера — точно так же она служит для связи ARM ядра и процессора периферии, а также на ней висит память и конечно, контроллер DMA.

К шине AHB подключены две периферийных шины — APB1 и APB2. Они равнозначны, просто обслуживают разные контроллеры интерфейсов. Частоты обоих шин APB1 и APB2 можно задавать собственными прескалерами относительно AHB, но их тоже можно оставить равными единице. По умолчанию после запуска микроконтроллера вся периферия на шинах APB1 и APB2 отключена в целях экономии энергии.

Интересующие нас контроллеры портов ввода-вывода висят на шине APB2.

Модель периферии в STM32

Вся периферия микроконтроллеров STM32 настраивается по стандартной процедуре.

  1. Включение тактирования соответствующего контроллера — буквально, подача на него тактового сигнала от шины APB;
  2. Настройки, специфичные для конкретной периферии — что-то записываем в управляющие регистры;
  3. Выбор источников прерываний — каждый периферийный блок может генерировать прерывания по разным поводам. Можно выбрать конкретные «поводы»;
  4. Назначение обработчика прерываний;
  5. Запуск контроллера.

Если прерывания не нужны — шаги 3 и 4 можно пропустить.

Вот, к примеру, инициализация таймера (указаны шаги из последовательности):

/* 1 */ RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
/* 2 */ TIM6->PSC = 24000;
TIM6->ARR = 1000;
/* 3 */ TIM6->DIER |= TIM_DIER_UIE;
/* 4 */ NVIC_EnableIRQ(TIM6_DAC_IRQn);
/* 5 */ TIM6->CR1 |= TIM_CR1_CEN;

Контроллер портов ввода-вывода

Наконец-то подобрались к основной теме статьи.

Так устроена одна нога ввода-вывода микроконтроллера STM32F100:

STM GPIO schem

Выглядит сложнее, чем в PIC или AVR icon smileНо на самом деле, ничего страшного.

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

Вход

Рассмотрим «вход». Сигнал напрямую идёт в линию «Analog», и если ножка настроена как вход АЦП или компаратора — и если эти блоки есть на этой ножке — сигнал напрямую попадает в них. Для работы с цифровыми сигналами установлен триггер Шмитта (это тот, который с гистерезисом), и его выход попадает в регистр-защёлку входных данных — вот теперь состояние ножки можно считать в программе, читая этот регистр (кстати, он называется IDR — input data register). Для обеспечения работы не-GPIO-периферии, висящей на этой ножке как на входе — сделан отвод под именем «Alternate function input». В качестве этой периферии может выступать UART/USART, SPI, USB да и очень многие другие контроллеры.

Важно понимать, что все эти отводы одновременно включены и работают, просто к ним может быть ничего не подключено.

Выход

Теперь «выход». Цифровые данные, записанные в порт как в выход, лежат в регистре ODR — output data register. Он доступен как на запись, так и на чтение. Читая из ODR, вы не читаете состояние ножки как входа! Вы читаете то, что сами в него записали.

Здесь же — выход от не-GPIO-периферии, под названием «Alternate function output», и попадаем в Output driver. Режим работы выхода с точки зрения схемотехники настраивается именно здесь — можно сделать пуш-пулл выход (линия жёстко притягивается к земле или питанию), выход с открытым коллектором (притягиваем линию к питанию, а землю обеспечивает что-то внешнее, висящее на контакте) или вовсе отключить выход. После драйвера в линию входит аналоговый выход от ЦАП, компаратора или ОУ, и попадаем снова в подтягивающие резисторы и диоды.

Драйвер цифрового выхода имеет также контроль крутизны, или скорости нарастания напряжения. Можно установить максимальную крутизну, и получить возможность дёргать ногой с частотой 50 МГц — но так мы получим и сильные электромагнитные помехи из-за резких звенящих фронтов. Можно установить минимальную крутизну, с максимальной частотой «всего» 2 МГц — но и значительно уменьшить радиопомехи.

На картинке можно заметить ещё один регистр, «Bit set/reset registers». Дело в том, что можно писать напрямую в регистр ODR, а можно использовать регистры BRR/BSRR. На самом деле, это очень крутая фича, о которой я расскажу дальше.

Возможности

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

Например, в более старших сериях можно настроить выход с открытым коллектором, и включить подтяжку к земле. Получается именно то, что нужно для шины 1-Wire. Правда, в серии STM32F1xx такой возможности нет, и нужно ставить внешний резистор подтяжки.

Атомарные операции

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

В STM32 эта проблема решена аппаратным путём — у вас есть регистры установки и сброса битов (BSRR и BRR), и здесь убиты сразу три зайца:

  1. не нужно читать порт для работы с ним
  2. для воздействия на конкретные пины нужно работать с конкретными битами, а не пытаться изменять весь порт
  3. эти операции атомарны — они проходят за один цикл, и их невозможно прервать посередине.

Подробнее про «конкретные биты» — каждый такт APB2 читаются регистры BSRR и BRR, и сразу же их содержимое применяется на регистр ODR, а сами эти регистры очищаются.Таким образом, если нужно установить 3 и 5 биты в порте — пишем в BSRR слово 10100, и всё успешно устанавливается.

Блокирование конфигурации

При желании, можно заблокировать конфигурацию любого пина от дальнейших изменений — любая попытка записи в регистр конфигурации окончится неуспехом. Это подойдёт для ответственных применений, где случайное переключение к примеру, выхода из режима open drain в push-pull выжжет всё подключенное к этому пину, или сам пин. Для включения блокирования предназначен регистр LCKR, только он снабжён защитой от случайной непреднамеренной записи — чтобы изменения вступили в силу, нужно подать специальную последовательность в бит LCKK.

Управляющие регистры

Всё управление контроллером GPIO сосредоточено в 32-битных регистрах GPIOx_RRR, где x — номер порта, а RRR — название регистра.

Младший конфигурационный регистр GPIOx_CRL

Регистр GPIO_CRL микроконтроллера STM32F100

Настраивает первые 8 ножек, с номерами 0..7. У каждой ножки два параметра, MODE и CNF.

MODE отвечает за режим вход/выход и скорость нарастания сигнала.

00 — вход (режим по умолчанию)

01 — выход со скоростью 10 МГц

10 — выход со скоростью 2 МГц

11 — выход со скоростью 50 МГц

CNF отвечает за конфигурацию пина.

  • В режиме входа (MODE=00):

    00 — аналоговый режим

    01 — плавающий вход (дефолт)

    10 — вход с подтяжкой к земле или питанию

    11 — зарезервирован

  • В режиме выхода (MODE=01, 10 или 11):

    00 — выход GPIO Push-pull

    01 — выход GPIO Open drain

    10 — выход альтернативной функции Push-pull

    11 — выход альтернативной функции Open drain

Старший конфигурационный регистр GPIOx_CRH

Регистр GPIO_CRH микроконтроллера STM32F100

Настраивает вторые 8 ножек, с номерами 8..15. Всё аналогично GPIOx_CRL.

Регистр входных данных GPIOx_IDR

Регистр GPIO_IDR микроконтроллера STM32F100

Каждый бит IDRy содержит в себе состояние соответствующей ножки ввода-вывода. Доступен только для чтения.

Регистр входных данных GPIOx_ODR

Регистр GPIO_ODR микроконтроллера STM32F100

Каждый бит ODRy содержит в себе состояние соответствующей ножки ввода-вывода. Можно записывать данные и они появятся на выходе порта, можно читать данные — читая предыдущее записанное значение.

Регистр атомарной установки/сброса битов выходных данных GPIOx_BSRR

Регистр GPIO_BSRR микроконтроллера STM32F100

Старшие 16 бит — для сброса соответствующих пинов в 0. 0 — ничего не делает, 1 — сбрасывает соответствующий бит. Младшие 16 бит — для установки битов в 1. Точно так же, запись «0» ничего не делает, запись «1» устанавливает соответствующий бит в 1.

Регистр только для записи — он сбрасывается в ноль на каждом такте APB2.

Регистр атомарного сброса битов выходных данных GPIOx_BRR

Регистр GPIO_BRR микроконтроллера STM32F100

Младшие 16 бит — для сброса соответствующих пинов. 0 — ничего не делает, 1 — сбрасывает соответствующий бит.

Регистр только для записи — он сбрасывается в ноль на каждом такте APB2.

Регистр блокирования конфигурации GPIOx_LCKR

Регистр GPIO_LCKR микроконтроллера STM32F100

Каждый бит LCKy блокирует соответствующие биты MODE/CNF регистров CRL/CRH от изменения, таким образом конфигурацию пина невозможно будет изменить вплоть до перезагрузки. Для активации блокирования необходимо записать блокирующую последовательность в бит LCKK: 1, 0, 1, читаем 0, читаем 1. Чтение бита LCKK сообщает текущий статус блокировки: 0 — блокировки нет, 1 — есть.

Работа в разных режимах

Режим входа

  • Отключается драйвер выхода
  • Входной триггер Шмитта включен
  • Резисторы подтяжек включаются по вашим настройкам, одно из трёх состояний — «вход, подтянутый к земле», «вход, подтянутый к питанию», или «плавающий вход»
  • Входной сигнал семплируется каждый такт шины APB2 и записывается в регистр IDR, и чтение этого регистра сообщает состояние ножки.

Режим выхода

  • Драйвер выхода включен, и действует так:

    В режиме «Push-Pull» работает как полумост, включая верхний транзистор в случае «1» и нижний в случае «0»,

    В режиме «Open drain» включает нижний транзистор в случае «0», а в случае «1» оставляет линию неподключенной (т.е. в третьем состоянии).

  • Входной триггер Шмитта включен
  • Отключаются резисторы подтяжек
  • Выходной сигнал семплируется каждый такт шины APB2 и записывается в регистр IDR, и чтение этого регистра сообщает состояние ножки в режиме Open drain.
  • Чтение регистра ODR сообщает последнее записанное состояние в режиме Push-Pull.

Режим альтернативной функции (не-GPIO-периферия)

  • Выходной драйвер — в режиме Push-Pull (к примеру, так работает ножка TX модуля USART) или Open drain, в зависимости от требований контроллера
  • Выходной драйвер управляется сигналами периферии, а не регистром ODR
  • Входной триггер Шмитта включен
  • Резисторы подтяжки отключены
  • Выходной сигнал семплируется каждый такт шины APB2 и записывается в регистр IDR, и чтение этого регистра сообщает состояние ножки в режиме Open drain.
  • Чтение регистра ODR сообщает последнее записанное состояние в режиме Push-Pull.

Аналоговый режим

  • Выходной драйвер выключен
  • Триггер Шмитта полностью отключается, чтобы не влиять на напряжение на входе
  • Резисторы подтяжки отключены
  • В регистре IDR — постоянно 0.

Вся внутренняя аналоговая периферия имеет высокий входной импеданс, поэтому и сама ножка по отношению к остальной схеме будет иметь высокий входной импеданс.

Наконец-то включаем светодиод

Теперь мы знаем всё, чтобы включить этот светодиод! Пойдём с самого начала.

Нужно включить тактирование GPIO порта. Поскольку мы используем светодиод на плате Discovery, выберем зелёный — он подключен к порту PC9. То есть, необходимо включить тактирование GPIOC.

RCC->APB2ENR= RCC_APB2ENR_IOPCEN;

Супер, теперь настраиваем порт на Push-pull выход со скоростью 2 МГц. Сначала выбираем режим, «Output mode, max speed 2 MHz» это 10 в регистре MODE. Достаточно просто установить второй бит.

GPIOC->CRH= GPIO_CRH_MODE9_1;

Теперь говорим про Push-pull выход. Это соответствует 00 в регистре CNF.

GPIOC->CRH &= !(GPIO_CRH_CNF9_0GPIO_CRH_CNF9_1);

Устанавливаем долгожданный бит в регистре BSRR!

GPIOC->BSRR= GPIO_BSRR_BS9;

Ну вот, честно говоря и всё. Напоследок — листинг мигающего светодиода icon smile

#include "stm32f10x.h"

int main(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
GPIOC->CRH &= !(GPIO_CRH_CNF9_0 | GPIO_CRH_CNF9_1);
GPIOC->CRH |= GPIO_CRH_MODE9_1;
uint32_t i, n=1000000;
while(1) {
GPIOC->BSRR |= GPIO_BSRR_BS9;
i=0; while(i++<n);
GPIOC->BRR |= GPIO_BRR_BR9;
i=0; while(i++<n);
}
}

Библиотека itacone

И всё-таки ещё не всё. Ради упрощения всяческих настроек я делаю библиотеку itacone. На текущий момент в ней реализована работа с GPIO-пинами и пара функций общего применения — но работа продолжается.

Читайте также:

комментария 2

  1. Павел:

    Интересная статья. Так глубоко ещё не добирался.

    Но пришлось по причине надобности выводить высокочастотный сигнал.

    Но вот у меня светодиоды зажигались и тухли только тогда, когда я заключил их цикл while. Иначе светодиод просто горел. (точнее мигал 8000000) раз в секунду. А пустой цикл компилятор отбрасывал как ненужную часть кода.
    МК STM32F103C8TG Среда Cube+Workspace

    Не знаю почему. Наверное это настроение компилятора.

  2. Павел:

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

    timertri = 0;
    while (timertri < 12) {
    if (timertri BSRR |= GPIO_BSRR_BS3;
    } else {
    // HAL_GPIO_WritePin(GPIOB, Lamp2_Pin, GPIO_PIN_RESET);
    GPIOB->BRR |= GPIO_BRR_BR3;
    }
    }

    На других сайтах увидел иные обращения к портам такие как :
    GPIOB->ODR &= ~ GPIO_ODR_ODR13; // зажечь светодиод
    GPIOB->ODR |= GPIO_ODR_ODR13; // погасить светодиод

    GPIOB -> BSRR = GPIO_BSRR_BR13; // сброс бита
    GPIOB -> BSRR = GPIO_BSRR_BS13; // установка бита

    которыми тоже что то зажигали
    и запутался в конец. особенно с этими (|= &=~).

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *