Низкоуровневый драйвер для конфигурации GPIO.

Платы, совместимые с Arduino, и предварительно написанные библиотеки для периферийных устройств снизили порог входа во встраиваемые системы для многих молодых инженеров и производителей. Но простой вызов функций и передача переменных становятся скучными и утомительными. Это случилось со мной на втором курсе моего инженерного курса еще в колледже. Поэтому я решил переключиться на ARM Cortex M и «Blue Pill» на базе STM32F103C8T6 от STMicroelectronics. Эта дешевая плата разработчика дает вам все базовые знания об аппаратном обеспечении, используемом в профессиональном дизайне встраиваемых систем, и помогает вам писать код на Embedded C и даже на ассемблере.

На YouTube есть множество видеороликов, посвященных установке любой IDE, которую вы хотите. Чтобы избавить вас от необходимости просматривать более 1000 страниц справочного руководства, ST предоставляет вам программное обеспечение для генерации кода, такое как STM32CubeMX, которое выдает код на основе HAL, предоставленного ST. Это отличный способ кодирования, если вы просто хотите заняться программированием и не хотите просматривать справочное руководство и примечания по применению.

Но это не то, чем я хотел заниматься. Я просмотрел справочное руководство и всю документацию, которую смог найти, и начал писать свои собственные библиотеки. Итак, в этом посте я рассказал, как вы можете написать свой собственный драйвер для настройки GPIO на ваших платах серии STM32F1x.

В зависимости от подкатегории вашего чипа количество портов будет варьироваться. Чипы серии STM32F1x имеют следующие порты GPIO: GPIOA, GPIOB, GPIOC, GPIOD, GPIOE, GPIOF и GPIOG. STM32F103C8T6 имеет порты до GPIOC. GPIOx подключается к процессору по шине APB2. Шина AHB делится на APB1 (36 МГц) и APB2 (72 МГц). GPIO мультиплексированы для обеспечения различных функций. Все контакты поддерживают прерывания, и функции могут быть переназначены другим контактам. Режимы, поддерживаемые GPIO:

  • Вход плавающий
  • Входной подтягивающий
  • Ввод-вытягивание
  • Аналоговый
  • Выход с открытым стоком
  • Выход двухтактный
  • Альтернативная функция «тяни-толкай»
  • Альтернативная функция с открытым стоком

Регистры, отвечающие за конфигурацию GPIO:

  • RCC_APB2ENR

  • EXTI_IMR

  • EXTI_RTSR

  • EXTI_FTSR

  • EXTI_PR

  • GPIOx_CRL

  • GPIOx_CRH

  • AFIO_EXTICR1

  • AFIO_EXTICR2

  • AFIO_EXTICR3

  • AFIO_EXTICR4

Начнем кодирование:

Драйвер GPIO состоит из двух файлов: GPIO.h и GPIO.c. В GPIO.h определены все макросы и функции, тогда как в GPIO.c функции реализованы. Драйвер GPIO имеет 3 функции: настройка GPIO, настройка прерывания и включение прерывания. Допустимые макросы для передачи:

#define GEN_PUSH_PULL_OUTPUT   0b0011
#define GEN_OPEN_DRAIN_OUTPUT  0b0111
#define ALT_PUSH_PULL_OUTPUT   0b1011
#define ALT_OPEN_DRAIN_OUTPUT  0b1111
#define ANALOG_INPUT           0b0000
#define FLOATING_INPUT         0b0100
#define PULL_DOWN_INPUT        0b1000
#define PULL_UP_INPUT          0b1000
 
#define Rising_Edge     1
#define Falling_Ege     2
#define Rise_n_Fall_Edge 3

Примечание. GPIO можно настроить на 3 допустимые скорости: 2 МГц, 10 МГц и 50 МГц. Для простоты программирования все выходы настроены на 50 МГц.

Настройка отдельных пинов

void GPIO_Pin_Setup(GPIO_TypeDef *port, int pin, int mode)

Эта функция устанавливает режим для отдельных контактов, как определено макросами в файле GPIO.h.

Поскольку конфигурация для GPIO одинакова для всех портов, можно повторить один и тот же оператор if для охвата всех GPIO, как показано ниже.

if(port == GPIOx){ 
if((mode == ALT_OPEN_DRAIN_OUTPUT)||(mode == ALT_PUSH_PULL_OUTPUT)) 
{  
    RCC->APB2ENR |= RCC_APB2ENR_IOPxEN | RCC_APB2ENR_AFIOEN; 
} 
else 
{  
    RCC->APB2ENR |= RCC_APB2ENR_IOPxEN; 
}  
if(pin < 8) 
{  
    port -> CRL |= mode << (4*pin); 
} 
else 
{  
    port -> CRH |= mode << (4*(pin - 8)); 
}

//GPIOx -> x[A:G]
//RCC_APB2ENR_IOPxEN -> x[A:G]

Настройка прерываний

void GPIO_Pin_Interrupt_Setup(GPIO_TypeDef *port, int pin, int type)

Эта функция устанавливает прерывания для отдельных контактов, а также тип триггера. Поскольку существует 16 внешних прерываний, и их можно настроить индивидуально, оператор «case» может быть реплицирован для всех контактов до 16 (от 0 до 15).

switch (pin) {
  case a:
  {
   if(port == GPIOA) AFIO->EXTICR[x] = AFIO_EXTICR[x+1]_EXTIa_PA;
   if(port == GPIOB) AFIO->EXTICR[x] = AFIO_EXTICR[x+1]_EXTIa_PB;
   if(port == GPIOC) AFIO->EXTICR[x] = AFIO_EXTICR[x+1]_EXTIa_PC;
   if(port == GPIOD) AFIO->EXTICR[x] = AFIO_EXTICR[x+1]_EXTIa_PD;
   if(port == GPIOE) AFIO->EXTICR[x] = AFIO_EXTICR[x+1]_EXTIa_PE;
   if(port == GPIOF) AFIO->EXTICR[x] = AFIO_EXTICR[x+1]_EXTIa_PF;
   if(port == GPIOG) AFIO->EXTICR[x] = AFIO_EXTICR[x+1]_EXTIa_PG;
break;
  }
}
if(type == Rising_Edge) EXTI->RTSR |= 1 << pin;
if(type == Falling_Ege) EXTI->FTSR |= 1 << pin;
if(type == Rise_n_Fall_Edge) 
{
  EXTI->RTSR |= 1 << pin;
  EXTI->FTSR |= 1 << pin;
}
//x -> [0:3]
//a -> pin

Включение прерываний

void GPIO_Interrupt_Enable(int pin, int priority, IRQn_Type irqnum)

Эта функция разрешает прерывания и устанавливает приоритет для этих прерываний. Приоритет прерывания варьируется от 0 (самый высокий) до 3 (самый низкий).

EXTI->IMR |= 1 << pin;
NVIC_SetPriority(irqnum,priority);
NVIC_EnableIRQ(irqnum);
//pin -> 0:15
//priority -> 0:3
//irqnum -> EXTIn_IRQn n=[0:15]
//acceptable irqnum -> EXTI0_IRQn, EXTI1_IRQn, EXTI2_IRQn, //EXTI3_IRQn, EXTI4_IRQn, EXTI9to5_IRQn, EXTI15to10_IRQn.

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

Код драйвера GPIO можно найти на моем GitHub.