STM32 HAL. Часть 1 — GPIO и внешние прерывания.

Приветствую коллеги и просто любопытствующие. Я несколько лет занимаюсь разработкой ПО для встраиваемых систем. В основном для STM32 с использованием Standart Peripheral Library. Недавно попытался пересесть на их HAL под названиемCUBE. Мягко говоря, разочаровался этим непродуманным продуктом и окончательно решил, что надо поделиться своим собственным HAL-ом, который накатывается поверх Standart Peripheral Library. В этой и, очень надеюсь, последующих статьях я выложу коды, опишу их и примеры их использования. Кому это интересно — прошу под кат.
В первой части я расскажу об использовании GPIO и внешних прерываний на этих самых GPIO.
Для начала нужно скачать HAL GPIO.
Далее необходимо определиться, будут ли использоваться внешние прерывания (с 0-ой по 15-ую линию). В зависимости от этого вheader-файле необходимо закомментировать, либо раскомментировать следующий дефайн


// использование внешних прерываний (закомментировать если не используются)
#define USE_EXTI

Затем необходимо создать нужные нам пины в source-файле как глобальные экземпляры структур. И затем использовать их посредством extern в других модулях.


// пин кнопки 1
sGpio gpioKey1 = {C, 12, HIGH, IN_PU};

// пин лампочки 1
sGpio gpioLed1 = {B, 7, HIGH, OUT_PP};

Где первый параметр — название порта, второй — номер пина, третий — скорость работы пина, четвертый — функция пина, пятый — при необходимости определяет ремап пина.
Функции пина могут быть как стандартными так и альтернативными:


// определение стандартных функций
#define OUT_PP      GPIO_Mode_Out_PP // выход с подтяжкой
#define OUT_OD      GPIO_Mode_Out_OD // выход с открытым коллектором
#define IN_PU       GPIO_Mode_IPU // вход подтянутый к питанию
#define IN_PD       GPIO_Mode_IPD           // вход подтянутый к земле
#define IN_FLOAT    GPIO_Mode_IN_FLOATING   // вход Hi-Z
#define IN_ADC      GPIO_Mode_AIN           // вход аналоговый

// определение альтернативных функций
#define USART_TX    GPIO_Mode_AF_PP         // USART отправитель 
#define USART_RX    IN_FLOAT                // USART приёмник
#define CAN_TX      GPIO_Mode_AF_PP         // CAN отправитель
#define CAN_RX      IN_PU                   // CAN приёмник
#define SPI_SCK     GPIO_Mode_AF_PP         // SPI тактирование
#define SPI_MOSI    GPIO_Mode_AF_PP         // SPI мастер -> слэйв
#define SPI_MISO    GPIO_Mode_AF_PP         // SPI слэйв -> мастер

Теперь остаётся лишь инициализировать пины там где это нужно.


 // инициализация пинов
 GPIOInit(&gpioKey1);
 GPIOInit(&gpioLed1);

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


 // конфигурация внешнего прерывания
 GPIOExtiConfig(2, LedToggle, (void*)&gpioLed1, &gpioKey1);

Где первый операнд — приоритет прерывания, второй — функция вызываемая при срабатывании прерывания, третий — операнд передаваемый в эту функцию, четвертый — пин для которого конфигурируется внешнее прерывание.
В использовании внешних прерываний есть пара ограничений:
1) Единовременно может быть сконфигурировано только по одному внешнему прерыванию на каждый номер пина, независимо от порта. То есть невозможно одновременно иметь внешнее прерывание на пине PB1 и PC1, а на PB1, PB2 и PC3 — без проблем.
2) Функция передаваемая в конфигуратор прерывания должна иметь входной операнд типа void*.

Теперь остаётся лишь включить сконфигурированное внешнее прерывание когда это понадобится


    // включение внешнего прерывания
    GPIOExtiEnable (eGpioTriger_FALL, &gpioKey1);

Где первый операнд — тип тригера внешнего прерывания, второй — пин внешнее прерывание которого включается.


// тип тригера
typedef enum
 {
   eGpioTriger_RISE  = EXTI_Trigger_Rising, // от земли к питанию
   eGpioTriger_FALL  = EXTI_Trigger_Falling,// от питания к земле
   eGpioTriger_ALL   = EXTI_Trigger_Rising_Falling,// оба варианта
 } eGpioTriger;

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

В завершение статьи коротко опишу, что делает пример идущий вместе с модулем (main.c в папке GPIO). Его функционал заключается в следующем: при нажатии на кнопку состояние светодиода переключается (вкл/выкл).


#include "GPIO\GPIO.h"

//========================================================================
//                          Глобальные переменные
//========================================================================

    // внешне определенные переменные
    extern sGpio        gpioKey1, 
                        gpioLed1;
    
//========================================================================
//              Переключение состояния светодиода
//-----------------------------------------------------------------------
//  gpioLed    - пин управления светодиодом
//========================================================================
    
void LedToggle (void* gpioLed)
{   
    GPIOToggle((sGpio*)gpioLed);
}

//========================================================================
//                         Основная программа
//========================================================================

int main (void)
{   
    // инициализация пинов
    GPIOInit(&gpioKey1);
    GPIOInit(&gpioLed1);
   
    // конфигурация внешнего прерывания
    GPIOExtiConfig(2, LedToggle, (void*)&gpioLed1, &gpioKey1);
    
    // включение внешнего прерывания
    GPIOExtiEnable (eGpioTriger_FALL, &gpioKey1);

    while(1) 
    {
      
    }
}

В примере создаётся 2 пина (вход подтянутый к питанию — считывает кнопку; выход с подтяжкой — запитывает светодиод). Оба пина инициализируются при старте программы. Следом конфигурируется внешнее прерывание, в которое передаётся функция переключающая состояние светодиода и указатель на пин запитывающий светодиод. То есть будь у нас 2 кнопки и 2 светодиода с аналогичным управлением, то при конфигурации внешнего прерывания была бы передана одна и та же функция, а отличие состояло бы в пинах запитывающих светодиоды и пинах на которые вешаются прерывания. Далее программа включает сконфигурированное внешнее прерывание используя вид тригера «падение от питания к земле» и уходит в бесконечный цикл делать ничего. При нажатии на кнопку входной пин подтягивается к земле, срабатывает прерывание, и обработчик вызывает функцию переключения состояния светодиода, передав в неё указатель на пин управляющий светодиодом. И в этой функции состояние этого пина инвертируется. Следовательно и состояние светодиода также инвертируется.

На этом всё. Прошу не судить строго. Это моя первая публикация. Изложил как смог. Основной материал собственно в самом коде, а не здесь. Приветствуются вопросы, предложения и прочие комментарии.
П.С. Также выкладываю проект GPIO project под ИАР с примером.

Источник: we.easyelectronics.ru