作者:sanjay 来源:CSDN
前言
本文主要学习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编程指南)
二、I/O 设备模型框架
RT-Thread 提供了一套简单的 I/O 设备模型框架,它位于硬件和应用程序之间,共分成三层,从上到下分别是 I/O 设备管理层、设备驱动框架层、设备驱动层。
I/O 设备模型框架(来源RT-Thread编程指南)
三、访问 PIN 设备
应用程序通过 RT-Thread 提供的 PIN 设备管理接口来访问 GPIO,相关接口如下所示:
1/* 设置引脚模式 */
2void rt_pin_mode(rt_base_t pin, rt_base_t mode);
3/* 设置引脚电平 */
4void rt_pin_write(rt_base_t pin, rt_base_t value);
5/* 读取引脚电平 */
6int rt_pin_read(rt_base_t pin);
7/* 绑定引脚中断回调函数 */
8rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args);
9/* 使能引脚中断 */
10rt_err_t rt_pin_detach_irq(rt_int32_t pin);
11/* 脱离引脚中断回调函数 */
12rt_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放这些引脚编号。
1/*
2*宏定义STM32引脚编号,
3*用户可以自己随便定义,
4*一般根据芯片封装的引脚顺序来定义
5*/
6
7/* LED & BEEP & KEY */
8#define PIN_BEEP 37 /* PB2 --> BEEP */
9#define PIN_LED_R 38 /* PE7 --> LED_R */
10#define PIN_LED_G 39 /* PE8 --> LED_G */
11#define PIN_LED_B 40 /* PE9 --> LED_B */
12#define PIN_KEY0 57 /* PD10 --> KEY0 */
13#define PIN_KEY1 56 /* PD9 --> KEY1 */
14#define PIN_KEY2 55 /* PD8 --> KEY2 */
15#define PIN_WK_UP 7 /* PC13 --> WK_UP */
STM32L475VE引脚编号示意图
(2)定义引脚信息
通过用C语言连接符来实现引脚信息的定义,然后再将每个引脚的信息按引脚的顺序存放在一个数组里面,这个数组的类型是引脚信息,用户可以新建一个drv_gpio.c来存放。
1/*
2*用C语言字符串连接定义引脚信息
3*index------->用户定义的PIN编号
4*gpio-------->如A代表GPIOA
5*gpio_index-->如1代表GPIO_PIN_7
6*例如__STM32_PIN(1, E, 2)表示1, GPIOE_CLK_ENABLE, GPIO_PIN_2
7*/
8#define __STM32_PIN(index, gpio, gpio_index) \
9{ \
10 index, GPIO##gpio##_CLK_ENABLE, GPIO##gpio, GPIO_PIN_##gpio_index \
11}
12/*
13*引脚不存在或引脚不能当做IO来用,
14*如第0引脚不存在,VSS或VBAT引脚不能当做IO来用
15*/
16#define __STM32_PIN_DEFAULT \
17{ \
18 -1, 0, 0, 0 \
19}
20
21/*
22*使能GPIO时钟
23*/
24static void GPIOA_CLK_ENABLE(void)
25{
26 __HAL_RCC_GPIOA_CLK_ENABLE();
27}
28
29static void GPIOB_CLK_ENABLE(void)
30{
31 __HAL_RCC_GPIOB_CLK_ENABLE();
32}
33
34static void GPIOC_CLK_ENABLE(void)
35{
36 __HAL_RCC_GPIOC_CLK_ENABLE();
37}
38
39static void GPIOD_CLK_ENABLE(void)
40{
41 __HAL_RCC_GPIOD_CLK_ENABLE();
42}
43
44static void GPIOE_CLK_ENABLE(void)
45{
46 __HAL_RCC_GPIOE_CLK_ENABLE();
47}
48
49/*
50*引脚信息
51*index------->用户定义的PIN编号
52*(*rcc)------>RCC时钟使能
53*gpio-------->如A代表GPIOA
54*gpio_index-->如1代表GPIO_PIN_7
55*/
56struct stm32_pin_index
57{
58 int index;
59 void (*rcc)(void);
60 GPIO_TypeDef *gpio;
61 uint32_t pin;
62};
63
64/*
65*stm32 pin引脚信息列表
66*写的时候需要把所有的信息信息都写进去,且安装封装的引脚顺序来写
67*/
68static const struct stm32_pin_index stm32_pins_table[] =
69{
70 __STM32_PIN_DEFAULT, /* 0 */
71 ......
72 __STM32_PIN_DEFAULT, /* 6, VBAT */
73 __STM32_PIN(7, C, 13), /* 7, PC13 --> WK_UP */
74 ......
75 __STM32_PIN(36, B, 1), /* 36, PB1 */
76 __STM32_PIN(37, B, 2), /* 37, PB2 --> BEEP */
77 __STM32_PIN(38, E, 7), /* 38, PE7 --> LED_R */
78 __STM32_PIN(39, E, 8), /* 39, PE8 --> LED_G */
79 __STM32_PIN(40, E, 9), /* 40, PE9 --> LED_B */
80 ......
81 __STM32_PIN(55, D, 8), /* 55, PD8 --> KEY2 */
82 __STM32_PIN(56, D, 9), /* 56, PD9 --> KEY1 */
83 __STM32_PIN(57, D, 10), /* 57, PD10 --> KEY0 */
84 ......
85 __STM32_PIN_DEFAULT, /* 94, BOOT0 */
86 ......
87};
(3)定义引脚中断信息
定义引脚中断信息主要包括引脚中断映射列表和引脚中断回调函数列表。
1/*
2*引脚中断映射
3*pinbit:如GPIO_PIN_0
4irqno:中断向量号,如EXTI0_IRQn
5*/
6struct stm32_pin_irq_map
7{
8 rt_uint16_t pinbit;
9 IRQn_Type irqno;
10};
11
12/*
13*引脚中断映射列表
14*/
15static const struct stm32_pin_irq_map stm32_pin_irq_map_tab[] =
16{
17 {GPIO_PIN_0, EXTI0_IRQn},
18 {GPIO_PIN_1, EXTI1_IRQn},
19 {GPIO_PIN_2, EXTI2_IRQn},
20 {GPIO_PIN_3, EXTI3_IRQn},
21 {GPIO_PIN_4, EXTI4_IRQn},
22 {GPIO_PIN_5, EXTI9_5_IRQn},
23 {GPIO_PIN_6, EXTI9_5_IRQn},
24 {GPIO_PIN_7, EXTI9_5_IRQn},
25 {GPIO_PIN_8, EXTI9_5_IRQn},
26 {GPIO_PIN_9, EXTI9_5_IRQn},
27 {GPIO_PIN_10, EXTI15_10_IRQn},
28 {GPIO_PIN_11, EXTI15_10_IRQn},
29 {GPIO_PIN_12, EXTI15_10_IRQn},
30 {GPIO_PIN_13, EXTI15_10_IRQn},
31 {GPIO_PIN_14, EXTI15_10_IRQn},
32 {GPIO_PIN_15, EXTI15_10_IRQn},
33};
34
35/*
36*引脚中断回调函数列表
37*pin---->引脚编号
38*mode--->中断触发模式
39*hdr---->中断回调函数,用户需要自行定义这个函数
40*args--->中断回调函数的入口参数,不需要时设置为 RT_NULL
41*用于绑定引脚中断回调函数
42*/
43static struct rt_pin_irq_hdr stm32_pin_irq_hdr_tab[] =
44{
45 /* pin, mode, hdr, args */
46 {-1, 0, RT_NULL, RT_NULL},
47 {-1, 0, RT_NULL, RT_NULL},
48 {-1, 0, RT_NULL, RT_NULL},
49 {-1, 0, RT_NULL, RT_NULL},
50 {-1, 0, RT_NULL, RT_NULL},
51 {-1, 0, RT_NULL, RT_NULL},
52 {-1, 0, RT_NULL, RT_NULL},
53 {-1, 0, RT_NULL, RT_NULL},
54 {-1, 0, RT_NULL, RT_NULL},
55 {-1, 0, RT_NULL, RT_NULL},
56 {-1, 0, RT_NULL, RT_NULL},
57 {-1, 0, RT_NULL, RT_NULL},
58 {-1, 0, RT_NULL, RT_NULL},
59 {-1, 0, RT_NULL, RT_NULL},
60 {-1, 0, RT_NULL, RT_NULL},
61 {-1, 0, RT_NULL, RT_NULL},
62};
2、获取引脚信息
获取引脚信息的思路:通过用户自己定义的STM32引脚编号到STM32 pins引脚列表查询对应的引脚信息。
1/*
2*计算pins列表或irq列表的索引数目
3*/
4#define ITEM_NUM(items) sizeof(items) / sizeof(items[0])
5
6/**************************************************************
7函数名称:stm32_get_pin
8函数功能:根据引脚编号获取引脚信息
9输入参数:pin:引脚编号
10返 回 值:从pins_tab列表获取的引脚信息
11备 注:无
12**************************************************************/
13static const struct stm32_pin_index *stm32_get_pin(uint8_t pin)
14{
15 const struct stm32_pin_index *index;
16
17 if(pin < ITEM_NUM(stm32_pins_tab)) /* 在stm32 pins_tab里面 */
18 {
19 index = &stm32_pins_tab[pin];
20 if(index->index == -1)
21 index = RT_NULL;
22 }
23 else
24 {
25 index = RT_NULL;
26 }
27
28 return index;
29}
3、设置引脚模式
在使用一个引脚之前都需要去设置一个引脚的模式,例如是输入还是输出,上拉还是下拉,是否开漏输入等。
1/**************************************************************
2函数名称:stm32_pin_mode
3函数功能:设置引脚模式
4输入参数:dev:设备对象,pin:引脚编号,mode:引脚模式
5返 回 值:无
6备 注:
7RT-Thread针对PIN设备做了5种引脚模式。
8输出模式:PIN_MODE_OUTPUT
9输入模式:PIN_MODE_INPUT
10上拉输入:PIN_MODE_INPUT_PULLUP
11下拉输入:PIN_MODE_INPUT_PULLDOWN
12开漏输出:PIN_MODE_OUTPUT_OD
13**************************************************************/
14static void stm32_pin_mode(rt_device_t dev, rt_base_t pin, rt_base_t mode)
15{
16 const struct stm32_pin_index *index;
17 GPIO_InitTypeDef GPIO_InitStruct;
18
19 index = stm32_get_pin(pin);/* 获取引脚信息 */
20 if(index == RT_NULL)
21 {
22 rt_kprintf("the pin does not exist\r\n");
23 return;/* 引脚不在pins列表里面,直接返回,不执行后面语句 */
24 }
25
26 index->rcc();/* 使能GPIO RCC时钟 */
27 GPIO_InitStruct.Pin = index->pin;/* 配置引脚 */
28 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;/* 配置引脚输出速度 */
29
30 /* 输出模式 */
31 if(mode == PIN_MODE_OUTPUT)
32 {
33 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
34 GPIO_InitStruct.Pull = GPIO_NOPULL;/* 不上拉或下拉 */
35 }
36 /* 输入模式 */
37 else if(mode == PIN_MODE_INPUT)
38 {
39 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; /* 输入模式 */
40 GPIO_InitStruct.Pull = GPIO_NOPULL;/* 不上拉或下拉 */
41 }
42 /* 上拉输入 */
43 else if(mode == PIN_MODE_INPUT_PULLUP)
44 {
45 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; /* 输入模式 */
46 GPIO_InitStruct.Pull = GPIO_PULLUP;/* 上拉 */
47 }
48 /* 下拉输入 */
49 else if(mode == PIN_MODE_INPUT_PULLDOWN)
50 {
51 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; /* 输入模式 */
52 GPIO_InitStruct.Pull = GPIO_PULLDOWN; /* 下拉 */
53 }
54 /* 开漏输出 */
55 else if (mode == PIN_MODE_OUTPUT_OD)
56 {
57 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;/* 开漏输出 */
58 GPIO_InitStruct.Pull = GPIO_NOPULL;/* 不上拉或下拉 */
59 }
60
61 HAL_GPIO_Init(index->gpio, &GPIO_InitStruct);/* GPIO初始化 */
62}
4、设置引脚电平
引脚电平可以设置2种宏定义值之一:PIN_LOW 低电平,PIN_HIGH 高电平。
1/**************************************************************
2函数名称:stm32_pin_write
3函数功能:写引脚电平
4输入参数:dev:设备对象,pin:引脚编号,value:引脚值
5返 回 值:无
6备 注:无
7**************************************************************/
8void stm32_pin_write(rt_device_t dev, rt_base_t pin, rt_base_t value)
9{
10 const struct stm32_pin_index *index;
11
12 index = stm32_get_pin(pin);/* 获取引脚信息 */
13 if(index == RT_NULL)
14 {
15 rt_kprintf("the pin does not exist\r\n");
16 return; /* 如果引脚不存在,直接返回,不执行下面语句 */
17 }
18
19 if(value == PIN_LOW)
20 {
21 HAL_GPIO_WritePin(index->gpio, index->pin, GPIO_PIN_RESET);/* 写引脚为低电平 */
22 }
23 else
24 {
25 HAL_GPIO_WritePin(index->gpio, index->pin, GPIO_PIN_SET);/* 写引脚为高电平 */
26 }
27}
28
29
5、读取引脚电平
读取引脚电平读取到的有2种宏定义之一:PIN_LOW 低电平,PIN_HIGH 高电平。
1/**************************************************************
2函数名称:stm32_pin_read
3函数功能:读引脚电平
4输入参数:dev:设备对象,pin:引脚编号
5返 回 值:value:读取到的引脚值,返回-1表示引脚不存在
6备 注:无
7**************************************************************/
8static int stm32_pin_read(rt_device_t dev, rt_base_t pin)
9{
10 int value;
11 const struct stm32_pin_index *index;
12
13 index = stm32_get_pin(pin);/* 获取引脚信息 */
14 if(index == RT_NULL)
15 {
16 rt_kprintf("the pin does not exist\r\n");
17 return -1;/* 如果引脚不存在则直接返回-1,不执行下面语句 */
18 }
19
20 value = HAL_GPIO_ReadPin(index->gpio, index->pin);
21
22 return value;
23}
6、根据引脚得到对应的中断线
根据引脚编号来得到对应的中断线和GPIO_PIN思路:首先是通过引脚编号来得到具体是哪个GPIO_PIN,然后再根据这个GPIO_PIN去得到对应的中断线,例如上面的KEY2引脚编号是55,然后等到GPIO_PIN_8,接着就得到EXTI9_5_IRQn中断线了。
1/**************************************************************
2函数名称:bit2bitno
3函数功能:将GPIO_PIN_X转换为中断列表的索引号
4输入参数:bit:GPIO_PIN_X,如GPIO_PIN_1
5返 回 值:返回中断列表的索引号
6备 注:在stm32l4xx_hal_gpio.h中,引脚定义是这样的:
7如:#define GPIO_PIN_0 ((uint16_t)0x0001),
8而后面的irq传入的是这些,将GPIO_PIN_X转换为中断列表的索引号。
9**************************************************************/
10rt_inline rt_int32_t bit2bitno(rt_uint32_t bit)
11{
12 int i;
13
14 for(i = 0; i < 32; i++)
15 {
16 if((0x01 << i) == bit)
17 {
18 return i;
19 }
20 }
21 return -1;
22}
23
24/**************************************************************
25函数名称:get_stm32_pin_irq_map
26函数功能:根据引脚得到对应的中断线
27输入参数:pinbit:引脚GPIO_PIN_X,如GPIO_PIN_0
28返 回 值:返回对应的中断线和GPIO_PIN
29备 注:无
30**************************************************************/
31rt_inline const struct stm32_pin_irq_map *get_stm32_pin_irq_map(uint32_t pinbit)
32{
33 rt_int32_t mapindex = bit2bitno(pinbit);
34
35 if(mapindex < 0 || mapindex >= ITEM_NUM(stm32_pin_irq_map_tab))
36 {
37 return RT_NULL;
38 }
39 return &stm32_pin_irq_map_tab[mapindex];
40}
7、绑定引脚中断回调函数
若要使用到引脚的中断功能,可以使用如下函数将某个引脚配置为某种中断触发模式并绑定一个中断回调函数到对应引脚,当引脚中断发生时,就会执行回调函数。
1/**************************************************************
2函数名称:stm32_pin_attach_irq
3函数功能:绑定引脚中断回调函数
4输入参数:
5*dev---->设备对象
6*pin---->引脚编号
7*mode--->中断触发模式
8*hdr---->中断回调函数,用户需要自行定义这个函数
9*args--->中断回调函数的参数,不需要时设置为 RT_NULL
10返 回 值:RT_EOK:绑定成功,错误码:绑定失败
11备 注:RT-Thread提供了5种中断触发模式。
12上升沿触发:PIN_IRQ_MODE_RISING
13下降沿触发:PIN_IRQ_MODE_FALLING
14边沿触发:PIN_IRQ_MODE_RISING_FALLING
15高电平触发:PIN_IRQ_MODE_HIGH_LEVEL
16低电平触发:PIN_IRQ_MODE_LOW_LEVEL
17**************************************************************/
18static 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)
19{
20 const struct stm32_pin_index *index;
21 rt_base_t level;
22 rt_int32_t irqindex = -1;
23
24 index = stm32_get_pin(pin); /* 获取引脚信息 */
25 if(index == RT_NULL)
26 {
27 return RT_ENOSYS;/* 不存在引脚,返回错误码 */
28 }
29
30 irqindex = bit2bitno(index->pin);/* 根据GPIO_PIN_X转换为irq索引号 */
31 if(irqindex < 0 || irqindex >= ITEM_NUM(stm32_pin_irq_map_tab))
32 {
33 return RT_ENOSYS;/* 不存在对应的irq引脚,返回错误码 */
34 }
35
36 level = rt_hw_interrupt_disable();
37 /* 已经绑定引脚中断回调函数 */
38 if(stm32_pin_irq_hdr_tab[irqindex].pin == pin &&
39 stm32_pin_irq_hdr_tab[irqindex].hdr == hdr &&
40 stm32_pin_irq_hdr_tab[irqindex].mode == mode &&
41 stm32_pin_irq_hdr_tab[irqindex].args == args)
42 {
43 rt_hw_interrupt_enable(level);
44 return RT_EOK;
45 }
46 if(stm32_pin_irq_hdr_tab[irqindex].pin != -1)
47 {
48 rt_hw_interrupt_enable(level);
49 return RT_EBUSY;
50 }
51 stm32_pin_irq_hdr_tab[irqindex].pin = pin;
52 stm32_pin_irq_hdr_tab[irqindex].hdr = hdr;
53 stm32_pin_irq_hdr_tab[irqindex].mode = mode;
54 stm32_pin_irq_hdr_tab[irqindex].args = args;
55 rt_hw_interrupt_enable(level);
56
57 return RT_EOK;
58}
8、使能引脚中断
绑定好引脚中断回调函数后使用下面的函数使能引脚中断
1/**************************************************************
2函数名称:stm32_pin_dettach_irq
3函数功能:脱离引脚中断回调函数
4输入参数:
5*dev------->设备对象
6*pin------->引脚编号
7*enabled--->PIN_IRQ_ENABLE(开启),PIN_IRQ_DISABLE(关闭)
8返 回 值:RT_EOK:使能成功,错误码:使能失败
9备 注:无
10**************************************************************/
11static rt_err_t stm32_pin_irq_enable(rt_device_t dev, rt_base_t pin, rt_uint32_t enabled)
12{
13 const struct stm32_pin_index *index;
14 const struct stm32_pin_irq_map *irqmap;
15 rt_base_t level;
16 rt_int32_t irqindex = -1;
17 GPIO_InitTypeDef GPIO_InitStruct;
18
19 index = stm32_get_pin(pin);
20 if(index == RT_NULL)
21 {
22 return RT_ENOSYS;
23 }
24
25 if(enabled == PIN_IRQ_ENABLE)/* 开启 */
26 {
27 irqindex = bit2bitno(index->pin);
28 if (irqindex < 0 || irqindex >= ITEM_NUM(stm32_pin_irq_map_tab))
29 {
30 return RT_ENOSYS;
31 }
32
33 level = rt_hw_interrupt_disable();
34
35 if(stm32_pin_irq_hdr_tab[irqindex].pin == -1)
36 {
37 rt_hw_interrupt_enable(level);
38 return RT_ENOSYS;
39 }
40
41 irqmap = &stm32_pin_irq_map_tab[irqindex];
42
43 /* 使能GPIO RCC时钟 */
44 index->rcc();
45
46 /* GPIO配置 */
47 GPIO_InitStruct.Pin = index->pin;
48 GPIO_InitStruct.Pull = GPIO_NOPULL;
49 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
50 switch(stm32_pin_irq_hdr_tab[irqindex].mode)
51 {
52 case PIN_IRQ_MODE_RISING:
53 GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; /* 上升沿触发 */
54 break;
55 case PIN_IRQ_MODE_FALLING:
56 GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; /* 下降沿触发 */
57 break;
58 case PIN_IRQ_MODE_RISING_FALLING:
59 GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;/* 边沿触发 */
60 break;
61 }
62 HAL_GPIO_Init(index->gpio, &GPIO_InitStruct);
63 HAL_NVIC_SetPriority(irqmap->irqno, 5, 0);
64 HAL_NVIC_EnableIRQ(irqmap->irqno);
65 rt_hw_interrupt_enable(level);
66 }
67 else if(enabled == PIN_IRQ_DISABLE)/* 关闭 */
68 {
69 irqmap = get_stm32_pin_irq_map(index->pin);
70 if (irqmap == RT_NULL)
71 {
72 return RT_ENOSYS;
73 }
74
75 HAL_NVIC_DisableIRQ(irqmap->irqno);
76 }
77 else
78 {
79 return RT_ENOSYS;
80 }
81
82 return RT_EOK;
83}
9、脱离引脚中断回调函数
引脚脱离了中断回调函数以后,中断并没有关闭,还可以调用绑定中断回调函数再次绑定其他回调函数。
1/**************************************************************
2函数名称:stm32_pin_dettach_irq
3函数功能:脱离引脚中断回调函数
4输入参数:
5*dev---->设备对象
6*pin---->引脚编号
7返 回 值:RT_EOK:脱离成功,错误码:脱离失败
8备 注:无
9**************************************************************/
10static rt_err_t stm32_pin_dettach_irq(rt_device_t dev, rt_int32_t pin)
11{
12 const struct stm32_pin_index *index;
13 rt_base_t level;
14 rt_int32_t irqindex = -1;
15
16 index = stm32_get_pin(pin);/* 获取引脚信息 */
17 if(index == RT_NULL)
18 {
19 return RT_ENOSYS;/* 不存在引脚,返回错误码 */
20 }
21
22 irqindex = bit2bitno(index->pin);
23 if(irqindex < 0 || irqindex >= ITEM_NUM(stm32_pin_irq_map_tab))
24 {
25 return RT_ENOSYS;/* 不存在对应的irq引脚,返回错误码 */
26 }
27 level = rt_hw_interrupt_disable();
28 if(stm32_pin_irq_hdr_tab[irqindex].pin == -1)
29 {
30 rt_hw_interrupt_enable(level);
31 return RT_EOK;
32 }
33 stm32_pin_irq_hdr_tab[irqindex].pin = -1;
34 stm32_pin_irq_hdr_tab[irqindex].hdr = RT_NULL;
35 stm32_pin_irq_hdr_tab[irqindex].mode = 0;
36 stm32_pin_irq_hdr_tab[irqindex].args = RT_NULL;
37 rt_hw_interrupt_enable(level);
38
39 return RT_EOK;
40}
10、注册 PIN设备
需要将前面的 PIN设备相关函数注册到RT-Thread中才能正常使用。
1/*
2*注册stm32 pin设备
3*/
4const static struct rt_pin_ops _stm32_pin_ops =
5{
6 stm32_pin_mode,
7 stm32_pin_write,
8 stm32_pin_read,
9 stm32_pin_attach_irq,
10 stm32_pin_dettach_irq,
11 stm32_pin_irq_enable,
12};
13/*
14*设备名称为:pin
15*/
16int rt_hw_pin_init(void)
17{
18 return rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL);
19}
(1)rt_pin_ops
如下,因此在编写函数时,应该和下面的这些函数对应,包括入口参数等。
1struct rt_pin_ops
2{
3 void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_base_t mode);
4 void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_base_t value);
5 int (*pin_read)(struct rt_device *device, rt_base_t pin);
6
7 /* TODO: add GPIO interrupt */
8 rt_err_t (*pin_attach_irq)(struct rt_device *device, rt_int32_t pin,
9 rt_uint32_t mode, void (*hdr)(void *args), void *args);
10 rt_err_t (*pin_detach_irq)(struct rt_device *device, rt_int32_t pin);
11 rt_err_t (*pin_irq_enable)(struct rt_device *device, rt_base_t pin, rt_uint32_t enabled);
12};
11、执行中断回调函数
需要先对RT-Thread中断部分有所了解。
1/*
2*获取中断回调函数
3*/
4rt_inline void pin_irq_hdr(int irqno)
5{
6 if(stm32_pin_irq_hdr_tab[irqno].hdr)
7 {
8 stm32_pin_irq_hdr_tab[irqno].hdr(stm32_pin_irq_hdr_tab[irqno].args);
9 }
10}
11
12/*
13*执行回调函数
14*/
15void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
16{
17 pin_irq_hdr(bit2bitno(GPIO_Pin));
18}
19
20void EXTI0_IRQHandler(void)
21{
22 rt_interrupt_enter();
23 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
24 rt_interrupt_leave();
25}
26
27void EXTI1_IRQHandler(void)
28{
29 rt_interrupt_enter();
30 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
31 rt_interrupt_leave();
32}
33
34void EXTI2_IRQHandler(void)
35{
36 rt_interrupt_enter();
37 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
38 rt_interrupt_leave();
39}
40
41void EXTI3_IRQHandler(void)
42{
43 rt_interrupt_enter();
44 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3);
45 rt_interrupt_leave();
46}
47
48void EXTI4_IRQHandler(void)
49{
50 rt_interrupt_enter();
51 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
52 rt_interrupt_leave();
53}
54
55void EXTI9_5_IRQHandler(void)
56{
57 rt_interrupt_enter();
58 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
59 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_6);
60 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_7);
61 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8);
62 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_9);
63 rt_interrupt_leave();
64}
65
66void EXTI15_10_IRQHandler(void)
67{
68 rt_interrupt_enter();
69 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10);
70 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);
71 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
72 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
73 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
74 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
75 rt_interrupt_leave();
76}
四、基于STM32的PIN 设备使用示例
前面主要是对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 代码
1/* led.h */
2
3#ifndef __LED_H__
4#define __LED_H__
5
6#include "pin.h"
7#include "drv_gpio.h"
8
9/* RGB灯接口定义 */
10#define LED_R(n) (n ? rt_pin_write(PIN_LED_R, PIN_HIGH) : rt_pin_write(PIN_LED_R, PIN_LOW))
11#define LED_G(n) (n ? rt_pin_write(PIN_LED_G, PIN_HIGH) : rt_pin_write(PIN_LED_G, PIN_LOW))
12#define LED_B(n) (n ? rt_pin_write(PIN_LED_B, PIN_HIGH) : rt_pin_write(PIN_LED_B, PIN_LOW))
13
14void led_init(void);
15
16
17#endif
1/* led.c */
2
3#include "led.h"
4
5
6/**************************************************************
7函数名称:led_init
8函数功能:使用RT-Thread PIN设备驱动初始化led
9输入参数:无
10返 回 值:无
11备 注:LED_R:PE7
12 LED_G:PE8
13 LED_B:PE9
14**************************************************************/
15void led_init(void)
16{
17 /* 都配置为输出模式 */
18 rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT);
19 rt_pin_mode(PIN_LED_G, PIN_MODE_OUTPUT);
20 rt_pin_mode(PIN_LED_B, PIN_MODE_OUTPUT);
21
22 /* 都配置为输出高电平 */
23 rt_pin_write(PIN_LED_R, PIN_HIGH);
24 rt_pin_write(PIN_LED_G, PIN_HIGH);
25 rt_pin_write(PIN_LED_B, PIN_HIGH);
26}
(2)key.h 和 key.c代码
1/* key.h */
2
3#ifndef __KEY_H__
4#define __KEY_H__
5
6#include "rtthread.h"
7#include "pin.h"
8#include "drv_gpio.h"
9
10#define KEY0 rt_pin_read(PIN_KEY0)
11#define KEY1 rt_pin_read(PIN_KEY1)
12#define KEY2 rt_pin_read(PIN_KEY2)
13#define WK_UP rt_pin_read(PIN_WK_UP)
14
15enum KEY_PRES
16{
17 NO_PRES = 0,
18 KEY0_PRES = 1,
19 KEY1_PRES = 2,
20 KEY2_PRES = 3,
21 WKUP_PRES = 4
22};
23
24void key_init(void);
25rt_uint8_t key_scan(rt_uint8_t mode);
26
27#endif
1#include "key.h"
2
3
4/**************************************************************
5函数名称:key_init
6函数功能:用RT-Thread PIN设备驱动初始化按键
7输入参数:无
8返 回 值:无
9备 注:KEY0:PD10
10 KEY1:PD9
11 KEY2:PD8
12 WK_UP:PC13
13**************************************************************/
14void key_init(void)
15{
16 /* 配置WK_UP按键为上拉输入 */
17 rt_pin_mode(PIN_WK_UP, PIN_MODE_INPUT_PULLUP);
18
19 /* 配置KEY0、KEY1、KEY2按键为下拉输入 */
20 rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLDOWN);
21 rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT_PULLDOWN);
22 rt_pin_mode(PIN_KEY2, PIN_MODE_INPUT_PULLDOWN);
23}
24
25/**************************************************************
26函数名称:key_scan
27函数功能:按键扫描
28输入参数:mode:1 --> 支持连按,0 --> 不支持连按
29返 回 值:按键值
30备 注:无
31**************************************************************/
32rt_uint8_t key_scan(rt_uint8_t mode)
33{
34 static rt_uint8_t key_up = 1; /* 按键松开标志 */
35
36 if(mode == 1)
37 {
38 key_up = 1; /* 支持连按 */
39 }
40 if(key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WK_UP == 1))
41 {
42 rt_thread_mdelay(10);
43 key_up = 0;
44
45 if(KEY0 == 0)
46 {
47 return KEY0_PRES;
48 }
49 else if(KEY1 == 0)
50 {
51 return KEY1_PRES;
52 }
53 else if(KEY2 == 0)
54 {
55 return KEY2_PRES;
56 }
57 else if(WK_UP == 1)
58 {
59 return WKUP_PRES;
60 }
61 }
62 else if(KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WK_UP == 0)
63 {
64 key_up = 1;
65 }
66
67 return NO_PRES; /* 无按键按下 */
68}
(3)'main.c'代码
1#include "main.h"
2#include "rtthread.h"
3
4#include "led.h"
5#include "key.h"
6
7int main(void)
8{
9 rt_uint8_t key;
10
11 while(1)
12 {
13 key = key_scan(0);
14
15 if(key == KEY2_PRES)
16 {
17 LED_B(0);
18 }
19 else if(key == WKUP_PRES)
20 {
21 LED_B(1);
22 }
23 rt_thread_mdelay(1);
24 }
25}
2、通过KEY来触发外部中断控制LED灯
通过按下KEY0来实现下降沿触发中断点亮LED红灯,通过按下KEY1来实现下降沿触发中断熄灭LED红灯,下面的exti_init需要放在board.c的rt_hw_board_init函数里进行开机初始化。
1/*
2回调函数
3*/
4void led_red_on(void *args)
5{
6 LED_R(0);
7}
8
9void led_red_off(void *args)
10{
11 LED_R(1);
12}
13
14/**************************************************************
15函数名称:exti_init
16函数功能:用RT-Thread的PIN设备驱动初始化外部中断
17输入参数:无
18返 回 值:无
19备 注:无
20**************************************************************/
21void exti_init(void)
22{
23 /* KEY0为上拉输入模式 */
24 rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP);
25 /* 绑定引脚中断回调函数,下降沿触发 */
26 rt_pin_attach_irq(PIN_KEY0, PIN_IRQ_MODE_FALLING, led_red_on, RT_NULL);
27 /* 使能中断 */
28 rt_pin_irq_enable(PIN_KEY0, PIN_IRQ_ENABLE);
29
30 /* KEY1为上拉输入模式 */
31 rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT_PULLUP);
32 /* 绑定引脚中断回调函数,下降沿触发 */
33 rt_pin_attach_irq(PIN_KEY1, PIN_IRQ_MODE_FALLING, led_red_off, RT_NULL);
34 /* 使能中断 */
35 rt_pin_irq_enable(PIN_KEY1, PIN_IRQ_ENABLE);
36}
3、通FinSH查找PIN设备
在finsh输入list_device可以查到注册到RT-Thread的PIN设备:
参考文献:
1、《RT-THREAD 编程指南》
NOW现在行动!
RT-Thread线下活动
* 活动抽赠书籍/开发板
1、【济南站】物联网操作系统RT-Thread基础入门免费培训:4月27日 (本周六),培训全程将以【理论+动手】方式进行,通过拆解一个DEMO实例,带你从内核到组件到软件包全面了解RT-Thread的体系框架。
2、【天津站】物联网操作系统RT-Thread基础入门免费培训:4月28日(本周日),培训内容同上。
扫码报名天津站
RT-Thread线上活动
1、【RT-Thread能力认证考试——RCEA】在6月份考试开始之前,我们将为报名参加的小伙伴提供为期4周的考前线上培训,并赠送官方教材一本。点击可查看详情,RAC能力认证:你未来就业晋升的通行证!
扫码报名
2、【RT-Thread x ST线下培训PPT】PPT就如何学习RT-Thread等方面进行了全面阐释,并以开发者产品——AGV小车进行了实际应用案例分析说明使用RT-Thread的好处!关注公众号后,在公众号后台回复关键词 “RTT和ST”即可获取下载链接!
你可以添加微信13924608367为好友,注明:公司+姓名,拉进 RT-Thread 官方微信交流群
RT-Thread
让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。
长按二维码,关注我们
点击阅读原文即可进入报名页
本文分享自微信公众号 - RTThread物联网操作系统(RTThread)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
来源:oschina
链接:https://my.oschina.net/u/4428324/blog/4619983