前言
本文主要学习RT-Thread的设备驱动框架之PIN 设备,这里以及后面更新的博客内容将不会详细介绍I/O 设备模型,当学习PIN 设备以及其他设备需要对I/O 设备模型有所了解,请和我一样刚学习RT-Thread的朋友们先自行到《RT-Thread编程指南》看一下I/O 设备模型。这里主要讲解如何访问PIN 设备,针对PIN设备各个函数讲解,以及教你如何基于PIN设备实现自己的GPIO驱动以及基于外部GPIO的外部中断实现,详细的gpio驱动文件的各个函数,学完PIN 设备就可以基于自己的开发板制作一个属于自己的bsp的GPIO驱动文件。本文讲的都是以STM32作为硬件设备的。
一、RT-Thread的架构
学习过FreeRTOS或UCOS的朋友都知道,这两个实时操作系统只有内核,RT-Thread不像FreeRTOS或UCOS, 它不仅仅有内核,还有设备驱动框架(如PIN 设备、I2C 设备、UART设备等)、丰富的上层组件和软件包,而软件包更是做了MQTT、LWM2M等协议,因此,RT-Thread是一个IoT OS,功能强大,这也是我为什么喜欢RT-Thread,学习RT-Thread的原因。
![RT-Thread 软件框架图(来源RT-Thread编程指南)](https://i-blog.csdnimg.cn/blog_migrate/e163106de4bb770fb1b9453045bda2eb.png)
二、I/O 设备模型框架
RT-Thread 提供了一套简单的 I/O 设备模型框架,它位于硬件和应用程序之间,共分成三层,从上到下分别是 I/O 设备管理层、设备驱动框架层、设备驱动层。
![I/O 设备模型框架(来源RT-Thread编程指南)](https://i-blog.csdnimg.cn/blog_migrate/30215e449775df2e9c1ce6c5c96a8d62.png)
三、访问 PIN 设备
应用程序通过 RT-Thread 提供的 PIN 设备管理接口来访问 GPIO,相关接口如下所示:
/* 设置引脚模式 */
void rt_pin_mode(rt_base_t pin, rt_base_t mode);
/* 设置引脚电平 */
void rt_pin_write(rt_base_t pin, rt_base_t value);
/* 读取引脚电平 */
int rt_pin_read(rt_base_t pin);
/* 绑定引脚中断回调函数 */
rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args);
/* 脱离引脚中断回调函数 */
rt_err_t rt_pin_detach_irq(rt_int32_t pin);
/* 使能引脚中断 */
rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled);
1、定义引脚信息
定义引脚信息非常重要,因为在上述的访问GPIO接口函数都是通过获取引脚信息来得到引脚编号或GPIO_PIN_X,然后根据这个信息去具体的操作对应的PIN,执行相关功能。
(1)宏定义引脚编号
对于引脚编号,用户可以自己随便定义,一般根据芯片封装的引脚顺序来定义,以潘多拉开发板的STM32L475VET6为例,下面定义LED & BEEP & KEY引脚编号,用户可以自己新建一个drv_gpio.h放这些引脚编号。
/*
*宏定义STM32引脚编号,
*用户可以自己随便定义,
*一般根据芯片封装的引脚顺序来定义
*/
/* LED & BEEP & KEY */
#define PIN_BEEP 37 /* PB2 --> BEEP */
#define PIN_LED_R 38 /* PE7 --> LED_R */
#define PIN_LED_G 39 /* PE8 --> LED_G */
#define PIN_LED_B 40 /* PE9 --> LED_B */
#define PIN_KEY0 57 /* PD10 --> KEY0 */
#define PIN_KEY1 56 /* PD9 --> KEY1 */
#define PIN_KEY2 55 /* PD8 --> KEY2 */
#define PIN_WK_UP 7 /* PC13 --> WK_UP */
![STM32L475VE引脚编号示意图](https://i-blog.csdnimg.cn/blog_migrate/64c1d7879e025c87d4a32c349152925d.png)
(2)定义引脚信息
通过用C语言连接符来实现引脚信息的定义,然后再将每个引脚的信息按引脚的顺序存放在一个数组里面,这个数组的类型是引脚信息,用户可以新建一个drv_gpio.c来存放。
/*
*用C语言字符串连接定义引脚信息
*index------->用户定义的PIN编号
*gpio-------->如A代表GPIOA
*gpio_index-->如1代表GPIO_PIN_7
*例如__STM32_PIN(1, E, 2)表示1, GPIOE_CLK_ENABLE, GPIO_PIN_2
*/
#define __STM32_PIN(index, gpio, gpio_index) \
{ \
index, GPIO##gpio##_CLK_ENABLE, GPIO##gpio, GPIO_PIN_##gpio_index \
}
/*
*引脚不存在或引脚不能当做IO来用,
*如第0引脚不存在,VSS或VBAT引脚不能当做IO来用
*/
#define __STM32_PIN_DEFAULT \
{ \
-1, 0, 0, 0 \
}
/*
*使能GPIO时钟
*/
static void GPIOA_CLK_ENABLE(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
}
static void GPIOB_CLK_ENABLE(void)
{
__HAL_RCC_GPIOB_CLK_ENABLE();
}
static void GPIOC_CLK_ENABLE(void)
{
__HAL_RCC_GPIOC_CLK_ENABLE();
}
static void GPIOD_CLK_ENABLE(void)
{
__HAL_RCC_GPIOD_CLK_ENABLE();
}
static void GPIOE_CLK_ENABLE(void)
{
__HAL_RCC_GPIOE_CLK_ENABLE();
}
/*
*引脚信息
*index------->用户定义的PIN编号
*(*rcc)------>RCC时钟使能
*gpio-------->如A代表GPIOA
*gpio_index-->如1代表GPIO_PIN_7
*/
struct stm32_pin_index
{
int index;
void (*rcc)(void);
GPIO_TypeDef *gpio;
uint32_t pin;
};
/*
*stm32 pin引脚信息列表
*写的时候需要把所有的信息信息都写进去,且安装封装的引脚顺序来写
*/
static const struct stm32_pin_index stm32_pins_table[] =
{
__STM32_PIN_DEFAULT, /* 0 */
......
__STM32_PIN_DEFAULT, /* 6, VBAT */
__STM32_PIN(7, C, 13), /* 7, PC13 --> WK_UP */
......
__STM32_PIN(36, B, 1), /* 36, PB1 */
__STM32_PIN(37, B, 2), /* 37, PB2 --> BEEP */
__STM32_PIN(38, E, 7), /* 38, PE7 --> LED_R */
__STM32_PIN(39, E, 8), /* 39, PE8 --> LED_G */
__STM32_PIN(40, E, 9), /* 40, PE9 --> LED_B */
......
__STM32_PIN(55, D, 8), /* 55, PD8 --> KEY2 */
__STM32_PIN(56, D, 9), /* 56, PD9 --> KEY1 */
__STM32_PIN(57, D, 10), /* 57, PD10 --> KEY0 */
......
__STM32_PIN_DEFAULT, /* 94, BOOT0 */
......
};
(3)定义引脚中断信息
定义引脚中断信息主要包括引脚中断映射列表和引脚中断回调函数列表。
/*
*引脚中断映射
*pinbitÿ