GPIO STM32, альтернативный вариант

8f8b6a1c80896e5a3f382f3bc491e4b3.jpg

Когда в 2011 году я переходил c atmega8 на stm32, меня очень вдохновил проект opencm3. Но вдохновил не на его изучение, а на написание похожего. На сегодня в моём варианте почти библиотеки есть макросы регистров для микроконтроллеров серий stm32f10x и stm32f40x, stm8s003, nrf51, nrf52, rp2040, и cc2640/1310. Реально же протестирована из этого списка только stm32f103. Кроме регистров для 103-й я написал базовые функции для включения/выключения тактирования периферии и управления портами ввода-вывода. А также написаны примеры для USB профилей HID gamepad, HID keyboard и USB serial port. В этом же посте задокументирую функции портов и тактирования.

Речь идёт о файлах stm32/rcc.c, stm32/gpio.c и stm32/delay.c моей библиотеки для микроконтроллеров.

Тактирование периферии (rcc.h).

Как известно, периферия в STM32, как и во всех ARM, раскидана по нескольким шинам: AHB, APB1 и APB2. Потому, когда по быстрому хочешь накидать скетч, немного неудобно каждый раз вспоминать, на какой же шине находится нужная тебе периферия. Этот тупой вопрос решён тупым switch-case. Теперь достаточно вспомнить название периферии (например: DMA1, TIM2, ADC1, USART2, SPI1, IOPA, USB, I2C1) и передать его в функцию enablePeriphClock(uint16_t periph) или resetPeriphClock(uint16_t periph) для включения или отключения периферии. Полный список названий периферии можно подсмотреть там же, в заголовочнике rcc.h в enum. Единственное неудобство — это то, что файл содержит код одновременно для всех микроконтроллеров 100й серии и не предупредит, если вы захотите включить несуществующую в данном варианте периферию.

Порты ввода — вывода (gpio.h).

Во всех библиотеках для STM32 есть очень удобные функции вида gpio_set(port, pin) для установки и сброса любого количества пинов. Я же решил продолжить эту традицию ещё и для конфигурации. Таким образом, кроме традиционных gpioSet и gpioReset у меня имеется:

// тип порта (аналоговый ввод, цифровой ввод, вывод, вывод с открытым стоком) 
void gpioSetAnalogue(uint32_t port, uint32_t pin);
void gpioSetInput(uint32_t port, uint32_t pin);
void gpioSetPushPull(uint32_t port, uint32_t pin);
void gpioSetOpenDrain(uint32_t port, uint32_t pin);
// ручное управление или для периферии (сперва нужно выбрать тип порта) 
void gpioSetAlternativeF(uint32_t port, uint32_t pin);
// 20кОм резисторы подтяжки
void gpioSetPullUp(uint32_t port, uint32_t pin);
void gpioSetPullDown(uint32_t port, uint32_t pin);
// частота тактирования порта (максимальная по умолчанию).
void gpioSetOutput2M(uint32_t port, uint32_t pin);
void gpioSetOutput10M(uint32_t port, uint32_t pin);
void gpioSetOutput50M(uint32_t port, uint32_t pin);

Как вы видите, все они имеют такой же синтаксис port, pin . Таким образом не надо передавать никаких настроек, нужная конфигурация уже содержится в названии функции, а также можно инициализировать несколько пинов сразу.

Теперь об особенностях использования. При инициализации порта на выход (push pull или open drain) выбирается максимальная частота тактирования — 50МГц. Таким образом для конфигурации пина на выход или вход достаточно вызвать всего одну из четырёх верхних функций. Если же частота тактирования нужна другая, после дополнительно нужно вызвать функцию выбора частоты, но не перед. Также после выбора типа порта (аналоговый вход, цифровой вход, выход, выход с открытым стоком), можно вызвать функции активации периферии на этом порту (gpioSetAlternativeF) и функции выбора подтяжки к питанию или земле для входов (gpioSetPullUp/Down). Если вы думаете, что вы что то напутали ранее, тогда есть вариант gpioToDefault(GPIOx, GPIOALL) .

Также новый подход дал и новую фичу. Кроме классических gpioSet/Reset и getPort, есть функция gpioIsActive, возвращает единицу если хотя бы один из выбранных пинов активен.

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

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

Задержки (delay.h).

Куда же без задержек, мой вариант задержек отличается от вариантов любых других библиотек убогостю. Т.к. в нём нет ассемблерных вставок, а количество тактов в микросекунду подбиралось вручную на частоте 1 Гц. Есть задержки для микросекунд, миллисекунд и секунд (rough_delay_us, delay_ms, delay_s) название функции намекает на то, что точность задержки грубая.

Так же есть и таймерные функции задержки для stm32f103, работающие на втором таймере (timDelayNs, timDelayUs, timDelayMs) . Естественно, требуют инициализации перед использованием (timDelayInit()).

Проверка.

Функции испытаны в боевых условиях, здесь просто классический hello world, мигание портом PA1. Берём папку stm32, пишем в main.c вот это:

#include "gpio.h"
#include "delay.h"
#include "rcc.h"


int main(void) {
    enablePeriphClock(IOPA);
    gpioSetPushPull(GPIOA, GPIO1);

    while(1){
        gpioSet(GPIOA, GPIO1);
        delay_ms(500);
        gpioReset(GPIOA, GPIO1);
        delay_ms(500);
    }
}

Запускаем make, потом st-flash write bin/example.bin 0x8000000. Под виндой нео бессудьте.

Итог

Наверное все микроконтроллеры не приспособлены под какой либо общий API. Вспомните сами, скольких любителей среды arduino вы ткнули в libavr в своё время. STM32 хоть и намного сложнее восьмибиток, но по прежнему ей лучше управлять вручную регистрами. А какие либо стандартные функции бесполезны и мешают, либо вовсе невозможны. Я уже достаточно повозился с STM32, попробовал и АЦП по DMA и UART и SPI и I2C и таймеры в качестве ШИМ. И в итоге я подумал, что есть только четыре случая, когда стандартные функции действительно нужны и пригождаются, три из них и были указаны в этом посте. Позже добавлю функции записи flash, помню, приходилось хранить настройки в памяти программ stmки. Возможно будет и пятый случай — функции конфигурации портов, которые я уже не представляю, как буду писать.

© Habrahabr.ru