本文主要学习RT-Thread的设备驱动框架之PIN 设备,这里以及后面更新的博客内容将不会详细介绍I/O 设备模型,当学习PIN 设备以及其他设备需要对I/O 设备模型有所了解,请和我一样刚学习RT-Thread的朋友们先自行到《RT-Thread编程指南》看一下I/O 设备模型。这里主要讲解如何访问PIN 设备,针对PIN设备各个函数讲解,以及教你如何基于PIN设备实现自己的GPIO驱动以及基于外部GPIO的外部中断实现,详细的gpio驱动文件的各个函数,学完PIN 设备就可以基于自己的开发板制作一个属于自己的bsp的GPIO驱动文件。本文讲的都是以STM32作为硬件设备的。
学习过FreeRTOS或UCOS的朋友都知道,这两个实时操作系统只有内核,RT-Thread不像FreeRTOS或UCOS, 它不仅仅有内核,还有设备驱动框架(如PIN 设备、I2C 设备、UART设备等)、丰富的上层组件和软件包,而软件包更是做了MQTT、LWM2M等协议,因此,RT-Thread是一个IoT OS,功能强大,这也是我为什么喜欢RT-Thread,学习RT-Thread的原因。
RT-Thread 提供了一套简单的 I/O 设备模型框架,它位于硬件和应用程序之间,共分成三层,从上到下分别是 I/O 设备管理层、设备驱动框架层、设备驱动层。
应用程序通过 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 */
(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:如GPIO_PIN_0
irqno:中断向量号,如EXTI0_IRQn
*/
struct stm32_pin_irq_map
{
rt_uint16_t pinbit;
IRQn_Type irqno;
};
/*
*引脚中断映射列表
*/
static const struct stm32_pin_irq_map stm32_pin_irq_map_tab[] =
{
{GPIO_PIN_0, EXTI0_IRQn},
{GPIO_PIN_1, EXTI1_IRQn},
{GPIO_PIN_2, EXTI2_IRQn},
{GPIO_PIN_3, EXTI3_IRQn},
{GPIO_PIN_4, EXTI4_IRQn},
{GPIO_PIN_5, EXTI9_5_IRQn},
{GPIO_PIN_6, EXTI9_5_IRQn},
{GPIO_PIN_7, EXTI9_5_IRQn},
{GPIO_PIN_8, EXTI9_5_IRQn},
{GPIO_PIN_9, EXTI9_5_IRQn},
{GPIO_PIN_10, EXTI15_10_IRQn},
{GPIO_PIN_11, EXTI15_10_IRQn},
{GPIO_PIN_12, EXTI15_10_IRQn},
{GPIO_PIN_13, EXTI15_10_IRQn},
{GPIO_PIN_14, EXTI15_10_IRQn},
{GPIO_PIN_15, EXTI15_10_IRQn},
};
/*
*引脚中断回调函数列表
*pin---->引脚编号
*mode--->中断触发模式
*hdr---->中断回调函数,用户需要自行定义这个函数
*args--->中断回调函数的入口参数,不需要时设置为 RT_NULL
*用于绑定引脚中断回调函数
*/
static struct rt_pin_irq_hdr stm32_pin_irq_hdr_tab[] =
{
/* pin, mode, hdr, args */
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
{-1, 0, RT_NULL, RT_NULL},
};
2、获取引脚信息
获取引脚信息的思路:通过用户自己定义的STM32引脚编号到STM32 pins引脚列表查询对应的引脚信息。
/*
*计算pins列表或irq列表的索引数目
*/
#define ITEM_NUM(items) sizeof(items) / sizeof(items[0])
/**************************************************************
函数名称:stm32_get_pin
函数功能:根据引脚编号获取引脚信息
输入参数:pin:引脚编号
返 回 值:从pins_tab列表获取的引脚信息
备 注:无
**************************************************************/
static const struct stm32_pin_index *stm32_get_pin(uint8_t pin)
{
const struct stm32_pin_index *index;
if(pin < ITEM_NUM(stm32_pins_tab)) /* 在stm32 pins_tab里面 */
{
index = &stm32_pins_tab[pin];
if(index->index == -1)
index = RT_NULL;
}
else
{
index = RT_NULL;
}
return index;
}
3、设置引脚模式
在使用一个引脚之前都需要去设置一个引脚的模式,例如是输入还是输出,上拉还是下拉,是否开漏输入等。
/**************************************************************
函数名称:stm32_pin_mode
函数功能:设置引脚模式
输入参数:dev:设备对象,pin:引脚编号,mode:引脚模式
返 回 值:无
备 注:
RT-Thread针对PIN设备做了5种引脚模式。
输出模式:PIN_MODE_OUTPUT
输入模式:PIN_MODE_INPUT
上拉输入:PIN_MODE_INPUT_PULLUP
下拉输入:PIN_MODE_INPUT_PULLDOWN
开漏输出:PIN_MODE_OUTPUT_OD
**************************************************************/
static void stm32_pin_mode(rt_device_t dev, rt_base_t pin, rt_base_t mode)
{
const struct stm32_pin_index *index;
GPIO_InitTypeDef GPIO_InitStruct;
index = stm32_get_pin(pin);/* 获取引脚信息 */
if(index == RT_NULL)
{
rt_kprintf("the pin does not exist\r\n");
return;/* 引脚不在pins列表里面,直接返回,不执行后面语句 */
}
index->rcc();/* 使能GPIO RCC时钟 */
GPIO_InitStruct.Pin = index->pin;/* 配置引脚 */
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;/* 配置引脚输出速度 */
/* 输出模式 */
if(mode == PIN_MODE_OUTPUT)
{
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
GPIO_InitStruct.Pull = GPIO_NOPULL;/* 不上拉或下拉 */
}
/* 输入模式 */
else if(mode == PIN_MODE_INPUT)
{
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; /* 输入模式 */
GPIO_InitStruct.Pull = GPIO_NOPULL;/* 不上拉或下拉 */
}
/* 上拉输入 */
else if(mode == PIN_MODE_INPUT_PULLUP)
{
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; /* 输入模式 */
GPIO_InitStruct.Pull = GPIO_PULLUP;/* 上拉 */
}
/* 下拉输入 */
else if(mode == PIN_MODE_INPUT_PULLDOWN)
{
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; /* 输入模式 */
GPIO_InitStruct.Pull = GPIO_PULLDOWN; /* 下拉 */
}
/* 开漏输出 */
else if (mode == PIN_MODE_OUTPUT_OD)
{
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;/* 开漏输出 */
GPIO_InitStruct.Pull = GPIO_NOPULL;/* 不上拉或下拉 */
}
HAL_GPIO_Init(index->gpio, &GPIO_InitStruct);/* GPIO初始化 */
}
4、设置引脚电平
引脚电平可以设置2种宏定义值之一:PIN_LOW 低电平,PIN_HIGH 高电平。
/**************************************************************
函数名称:stm32_pin_write
函数功能:写引脚电平
输入参数:dev:设备对象,pin:引脚编号,value:引脚值
返 回 值:无
备 注:无
**************************************************************/
void stm32_pin_write(rt_device_t dev, rt_base_t pin, rt_base_t value)
{
const struct stm32_pin_index *index;
index = stm32_get_pin(pin);/* 获取引脚信息 */
if(index == RT_NULL)
{
rt_kprintf("the pin does not exist\r\n");
return; /* 如果引脚不存在,直接返回,不执行下面语句 */
}
if(value == PIN_LOW)
{
HAL_GPIO_WritePin(index->gpio, index->pin, GPIO_PIN_RESET);/* 写引脚为低电平 */
}
else
{
HAL_GPIO_WritePin(index->gpio, index->pin, GPIO_PIN_SET);/* 写引脚为高电平 */
}
}
5、读取引脚电平
读取引脚电平读取到的有2种宏定义之一:PIN_LOW 低电平,PIN_HIGH 高电平。
/**************************************************************
函数名称:stm32_pin_read
函数功能:读引脚电平
输入参数:dev:设备对象,pin:引脚编号
返 回 值:value:读取到的引脚值,返回-1表示引脚不存在
备 注:无
**************************************************************/
static int stm32_pin_read(rt_device_t dev, rt_base_t pin)
{
int value;
const struct stm32_pin_index *index;
index = stm32_get_pin(pin);/* 获取引脚信息 */
if(index == RT_NULL)
{
rt_kprintf("the pin does not exist\r\n");
return -1;/* 如果引脚不存在则直接返回-1,不执行下面语句 */
}
value = HAL_GPIO_ReadPin(index->gpio, index->pin);
return value;
}
6、根据引脚得到对应的中断线
根据引脚编号来得到对应的中断线和GPIO_PIN思路:首先是通过引脚编号来得到具体是哪个GPIO_PIN,然后再根据这个GPIO_PIN去得到对应的中断线,例如上面的KEY2引脚编号是55,然后等到GPIO_PIN_8,接着就得到EXTI9_5_IRQn中断线了。
/**************************************************************
函数名称:bit2bitno
函数功能:将GPIO_PIN_X转换为中断列表的索引号
输入参数:bit:GPIO_PIN_X,如GPIO_PIN_1
返 回 值:返回中断列表的索引号
备 注:在stm32l4xx_hal_gpio.h中,引脚定义是这样的:
如:#define GPIO_PIN_0 ((uint16_t)0x0001),
而后面的irq传入的是这些,将GPIO_PIN_X转换为中断列表的索引号。
**************************************************************/
rt_inline rt_int32_t bit2bitno(rt_uint32_t bit)
{
int i;
for(i = 0; i < 32; i++)
{
if((0x01 << i) == bit)
{
return i;
}
}
return -1;
}
/**************************************************************
函数名称:get_stm32_pin_irq_map
函数功能:根据引脚得到对应的中断线
输入参数:pinbit:引脚GPIO_PIN_X,如GPIO_PIN_0
返 回 值:返回对应的中断线和GPIO_PIN
备 注:无
**************************************************************/
rt_inline const struct stm32_pin_irq_map *get_stm32_pin_irq_map(uint32_t pinbit)
{
rt_int32_t mapindex = bit2bitno(pinbit);
if(mapindex < 0 || mapindex >= ITEM_NUM(stm32_pin_irq_map_tab))
{
return RT_NULL;
}
return &stm32_pin_irq_map_tab[mapindex];
}
7、绑定引脚中断回调函数
若要使用到引脚的中断功能,可以使用如下函数将某个引脚配置为某种中断触发模式并绑定一个中断回调函数到对应引脚,当引脚中断发生时,就会执行回调函数。
/**************************************************************
函数名称:stm32_pin_attach_irq
函数功能:绑定引脚中断回调函数
输入参数:
*dev---->设备对象
*pin---->引脚编号
*mode--->中断触发模式
*hdr---->中断回调函数,用户需要自行定义这个函数
*args--->中断回调函数的参数,不需要时设置为 RT_NULL
返 回 值:RT_EOK:绑定成功,错误码:绑定失败
备 注:RT-Thread提供了5种中断触发模式。
上升沿触发:PIN_IRQ_MODE_RISING
下降沿触发:PIN_IRQ_MODE_FALLING
边沿触发:PIN_IRQ_MODE_RISING_FALLING
高电平触发:PIN_IRQ_MODE_HIGH_LEVEL
低电平触发:PIN_IRQ_MODE_LOW_LEVEL
**************************************************************/
static rt_err_t stm32_pin_attach_irq(rt_device_t dev, rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args)
{
const struct stm32_pin_index *index;
rt_base_t level;
rt_int32_t irqindex = -1;
index = stm32_get_pin(pin); /* 获取引脚信息 */
if(index == RT_NULL)
{
return RT_ENOSYS;/* 不存在引脚,返回错误码 */
}
irqindex = bit2bitno(index->pin);/* 根据GPIO_PIN_X转换为irq索引号 */
if(irqindex < 0 || irqindex >= ITEM_NUM(stm32_pin_irq_map_tab))
{
return RT_ENOSYS;/* 不存在对应的irq引脚,返回错误码 */
}
level = rt_hw_interrupt_disable();
/* 已经绑定引脚中断回调函数 */
if(stm32_pin_irq_hdr_tab[irqindex].pin == pin &&
stm32_pin_irq_hdr_tab[irqindex].hdr == hdr &&
stm32_pin_irq_hdr_tab[irqindex].mode == mode &&
stm32_pin_irq_hdr_tab[irqindex].args == args)
{
rt_hw_interrupt_enable(level);
return RT_EOK;
}
if(stm32_pin_irq_hdr_tab[irqindex].pin != -1)
{
rt_hw_interrupt_enable(level);
return RT_EBUSY;
}
stm32_pin_irq_hdr_tab[irqindex].pin = pin;
stm32_pin_irq_hdr_tab[irqindex].hdr = hdr;
stm32_pin_irq_hdr_tab[irqindex].mode = mode;
stm32_pin_irq_hdr_tab[irqindex].args = args;
rt_hw_interrupt_enable(level);
return RT_EOK;
}
8、使能引脚中断
绑定好引脚中断回调函数后使用下面的函数使能引脚中断
/**************************************************************
函数名称:stm32_pin_dettach_irq
函数功能:脱离引脚中断回调函数
输入参数:
*dev------->设备对象
*pin------->引脚编号
*enabled--->PIN_IRQ_ENABLE(开启),PIN_IRQ_DISABLE(关闭)
返 回 值:RT_EOK:使能成功,错误码:使能失败
备 注:无
**************************************************************/
static rt_err_t stm32_pin_irq_enable(rt_device_t dev, rt_base_t pin, rt_uint32_t enabled)
{
const struct stm32_pin_index *index;
const struct stm32_pin_irq_map *irqmap;
rt_base_t level;
rt_int32_t irqindex = -1;
GPIO_InitTypeDef GPIO_InitStruct;
index = stm32_get_pin(pin);
if(index == RT_NULL)
{
return RT_ENOSYS;
}
if(enabled == PIN_IRQ_ENABLE)/* 开启 */
{
irqindex = bit2bitno(index->pin);
if (irqindex < 0 || irqindex >= ITEM_NUM(stm32_pin_irq_map_tab))
{
return RT_ENOSYS;
}
level = rt_hw_interrupt_disable();
if(stm32_pin_irq_hdr_tab[irqindex].pin == -1)
{
rt_hw_interrupt_enable(level);
return RT_ENOSYS;
}
irqmap = &stm32_pin_irq_map_tab[irqindex];
/* 使能GPIO RCC时钟 */
index->rcc();
/* GPIO配置 */
GPIO_InitStruct.Pin = index->pin;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
switch(stm32_pin_irq_hdr_tab[irqindex].mode)
{
case PIN_IRQ_MODE_RISING:
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; /* 上升沿触发 */
break;
case PIN_IRQ_MODE_FALLING:
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; /* 下降沿触发 */
break;
case PIN_IRQ_MODE_RISING_FALLING:
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;/* 边沿触发 */
break;
}
HAL_GPIO_Init(index->gpio, &GPIO_InitStruct);
HAL_NVIC_SetPriority(irqmap->irqno, 5, 0);
HAL_NVIC_EnableIRQ(irqmap->irqno);
rt_hw_interrupt_enable(level);
}
else if(enabled == PIN_IRQ_DISABLE)/* 关闭 */
{
irqmap = get_stm32_pin_irq_map(index->pin);
if (irqmap == RT_NULL)
{
return RT_ENOSYS;
}
HAL_NVIC_DisableIRQ(irqmap->irqno);
}
else
{
return RT_ENOSYS;
}
return RT_EOK;
}
9、脱离引脚中断回调函数
引脚脱离了中断回调函数以后,中断并没有关闭,还可以调用绑定中断回调函数再次绑定其他回调函数。
/**************************************************************
函数名称:stm32_pin_dettach_irq
函数功能:脱离引脚中断回调函数
输入参数:
*dev---->设备对象
*pin---->引脚编号
返 回 值:RT_EOK:脱离成功,错误码:脱离失败
备 注:无
**************************************************************/
static rt_err_t stm32_pin_dettach_irq(rt_device_t dev, rt_int32_t pin)
{
const struct stm32_pin_index *index;
rt_base_t level;
rt_int32_t irqindex = -1;
index = stm32_get_pin(pin);/* 获取引脚信息 */
if(index == RT_NULL)
{
return RT_ENOSYS;/* 不存在引脚,返回错误码 */
}
irqindex = bit2bitno(index->pin);
if(irqindex < 0 || irqindex >= ITEM_NUM(stm32_pin_irq_map_tab))
{
return RT_ENOSYS;/* 不存在对应的irq引脚,返回错误码 */
}
level = rt_hw_interrupt_disable();
if(stm32_pin_irq_hdr_tab[irqindex].pin == -1)
{
rt_hw_interrupt_enable(level);
return RT_EOK;
}
stm32_pin_irq_hdr_tab[irqindex].pin = -1;
stm32_pin_irq_hdr_tab[irqindex].hdr = RT_NULL;
stm32_pin_irq_hdr_tab[irqindex].mode = 0;
stm32_pin_irq_hdr_tab[irqindex].args = RT_NULL;
rt_hw_interrupt_enable(level);
return RT_EOK;
}
10、注册 PIN设备
需要将前面的 PIN设备相关函数注册到RT-Thread中才能正常使用。
/*
*注册stm32 pin设备
*/
const static struct rt_pin_ops _stm32_pin_ops =
{
stm32_pin_mode,
stm32_pin_write,
stm32_pin_read,
stm32_pin_attach_irq,
stm32_pin_dettach_irq,
stm32_pin_irq_enable,
};
/*
*设备名称为:pin
*/
int rt_hw_pin_init(void)
{
return rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL);
}
(1)rt_pin_ops如下,因此在编写函数时,应该和下面的这些函数对应,包括入口参数等。
struct rt_pin_ops
{
void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_base_t mode);
void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_base_t value);
int (*pin_read)(struct rt_device *device, rt_base_t pin);
/* TODO: add GPIO interrupt */
rt_err_t (*pin_attach_irq)(struct rt_device *device, rt_int32_t pin,
rt_uint32_t mode, void (*hdr)(void *args), void *args);
rt_err_t (*pin_detach_irq)(struct rt_device *device, rt_int32_t pin);
rt_err_t (*pin_irq_enable)(struct rt_device *device, rt_base_t pin, rt_uint32_t enabled);
};
11、执行中断回调函数
需要先对RT-Thread中断部分有所了解。
/*
*获取中断回调函数
*/
rt_inline void pin_irq_hdr(int irqno)
{
if(stm32_pin_irq_hdr_tab[irqno].hdr)
{
stm32_pin_irq_hdr_tab[irqno].hdr(stm32_pin_irq_hdr_tab[irqno].args);
}
}
/*
*执行回调函数
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
pin_irq_hdr(bit2bitno(GPIO_Pin));
}
void EXTI0_IRQHandler(void)
{
rt_interrupt_enter();
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
rt_interrupt_leave();
}
void EXTI1_IRQHandler(void)
{
rt_interrupt_enter();
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
rt_interrupt_leave();
}
void EXTI2_IRQHandler(void)
{
rt_interrupt_enter();
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
rt_interrupt_leave();
}
void EXTI3_IRQHandler(void)
{
rt_interrupt_enter();
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3);
rt_interrupt_leave();
}
void EXTI4_IRQHandler(void)
{
rt_interrupt_enter();
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
rt_interrupt_leave();
}
void EXTI9_5_IRQHandler(void)
{
rt_interrupt_enter();
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_6);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_7);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_9);
rt_interrupt_leave();
}
void EXTI15_10_IRQHandler(void)
{
rt_interrupt_enter();
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
rt_interrupt_leave();
}
前面主要是对RT-Thread PIN设备一些相关函数的讲解和注释,光说不练都是假把式,那么接下来就进行实验,用RTT&正点原子联合出品的潘多拉开发板进行实验,包括两个实验:(1)实现读取KEY按键的高低电平来控制LED灯,(2)实现KEY按键触发外部中断来控制LED灯。
1、读取按键来控制LED灯
实现读取到KEY2按下就点亮LED蓝灯,读取到WK_UP按下就熄灭蓝灯。下面实现的led_init和key_init都需要放在board.c的rt_hw_board_init函数里进行开机初始化。
(1)led.h 和led.c 代码:
/* led.h */
#ifndef __LED_H__
#define __LED_H__
#include "pin.h"
#include "drv_gpio.h"
/* RGB灯接口定义 */
#define LED_R(n) (n ? rt_pin_write(PIN_LED_R, PIN_HIGH) : rt_pin_write(PIN_LED_R, PIN_LOW))
#define LED_G(n) (n ? rt_pin_write(PIN_LED_G, PIN_HIGH) : rt_pin_write(PIN_LED_G, PIN_LOW))
#define LED_B(n) (n ? rt_pin_write(PIN_LED_B, PIN_HIGH) : rt_pin_write(PIN_LED_B, PIN_LOW))
void led_init(void);
#endif
/* led.c */
#include "led.h"
/**************************************************************
函数名称:led_init
函数功能:使用RT-Thread PIN设备驱动初始化led
输入参数:无
返 回 值:无
备 注:LED_R:PE7
LED_G:PE8
LED_B:PE9
**************************************************************/
void led_init(void)
{
/* 都配置为输出模式 */
rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT);
rt_pin_mode(PIN_LED_G, PIN_MODE_OUTPUT);
rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT);
/* 都配置为输出高电平 */
rt_pin_write(PIN_LED_R, PIN_HIGH);
rt_pin_write(PIN_LED_G, PIN_HIGH);
rt_pin_write(PIN_LED_B, PIN_HIGH);
}
(2)key.h 和 key.c代码
/* key.h */
#ifndef __KEY_H__
#define __KEY_H__
#include "rtthread.h"
#include "pin.h"
#include "drv_gpio.h"
#define KEY0 rt_pin_read(PIN_KEY0)
#define KEY1 rt_pin_read(PIN_KEY1)
#define KEY2 rt_pin_read(PIN_KEY2)
#define WK_UP rt_pin_read(PIN_WK_UP)
enum KEY_PRES
{
NO_PRES = 0,
KEY0_PRES = 1,
KEY1_PRES = 2,
KEY2_PRES = 3,
WKUP_PRES = 4
};
void key_init(void);
rt_uint8_t key_scan(rt_uint8_t mode);
#endif
#include "key.h"
/**************************************************************
函数名称:key_init
函数功能:用RT-Thread PIN设备驱动初始化按键
输入参数:无
返 回 值:无
备 注:KEY0:PD10
KEY1:PD9
KEY2:PD8
WK_UP:PC13
**************************************************************/
void key_init(void)
{
/* 配置WK_UP按键为上拉输入 */
rt_pin_mode(PIN_WK_UP, PIN_MODE_INPUT_PULLUP);
/* 配置KEY0、KEY1、KEY2按键为下拉输入 */
rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLDOWN);
rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT_PULLDOWN);
rt_pin_mode(PIN_KEY2, PIN_MODE_INPUT_PULLDOWN);
}
/**************************************************************
函数名称:key_scan
函数功能:按键扫描
输入参数:mode:1 --> 支持连按,0 --> 不支持连按
返 回 值:按键值
备 注:无
**************************************************************/
rt_uint8_t key_scan(rt_uint8_t mode)
{
static rt_uint8_t key_up = 1; /* 按键松开标志 */
if(mode == 1)
{
key_up = 1; /* 支持连按 */
}
if(key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WK_UP == 1))
{
rt_thread_mdelay(10);
key_up = 0;
if(KEY0 == 0)
{
return KEY0_PRES;
}
else if(KEY1 == 0)
{
return KEY1_PRES;
}
else if(KEY2 == 0)
{
return KEY2_PRES;
}
else if(WK_UP == 1)
{
return WKUP_PRES;
}
}
else if(KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WK_UP == 0)
{
key_up = 1;
}
return NO_PRES; /* 无按键按下 */
}
(3)main.c代码
#include "main.h"
#include "rtthread.h"
#include "led.h"
#include "key.h"
int main(void)
{
rt_uint8_t key;
while(1)
{
key = key_scan(0);
if(key == KEY2_PRES)
{
LED_B(0);
}
else if(key == WKUP_PRES)
{
LED_B(1);
}
rt_thread_mdelay(1);
}
}
2、通过KEY来触发外部中断控制LED灯
通过按下KEY0来实现下降沿触发中断点亮LED红灯,通过按下KEY1来实现下降沿触发中断熄灭LED红灯,下面的exti_init需要放在board.c的rt_hw_board_init函数里进行开机初始化。
/*
回调函数
*/
void led_red_on(void *args)
{
LED_R(0);
}
void led_red_off(void *args)
{
LED_R(1);
}
/**************************************************************
函数名称:exti_init
函数功能:用RT-Thread的PIN设备驱动初始化外部中断
输入参数:无
返 回 值:无
备 注:无
**************************************************************/
void exti_init(void)
{
/* KEY0为上拉输入模式 */
rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP);
/* 绑定引脚中断回调函数,下降沿触发 */
rt_pin_attach_irq(PIN_KEY0, PIN_IRQ_MODE_FALLING, led_red_on, RT_NULL);
/* 使能中断 */
rt_pin_irq_enable(PIN_KEY0, PIN_IRQ_ENABLE);
/* KEY1为上拉输入模式 */
rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT_PULLUP);
/* 绑定引脚中断回调函数,下降沿触发 */
rt_pin_attach_irq(PIN_KEY1, PIN_IRQ_MODE_FALLING, led_red_off, RT_NULL);
/* 使能中断 */
rt_pin_irq_enable(PIN_KEY1, PIN_IRQ_ENABLE);
}
3、通FinSH查找PIN设备
在finsh输入list_device可以查到注册到RT-Thread的PIN设备:
1、《RT-THREAD 编程指南》