以下通过pin驱动来大概介绍rtthread驱动程序的编写方法
1. RT_thread提供了下面的几个函数(PIN设备管理接口)来访问GPIO,如下:
函数 描述
rt_pin_mode() 设置引脚模式
rt_pin_write() 设置引脚电平
rt_pin_read() 读取引脚电平
rt_pin_attach_irq() 绑定引脚中断回调函数
rt_pin_irq_enable() 使能引脚中断
rt_pin_detach_irq() 脱离引脚中断回调函数
2. 首先rtthread的驱动主要存放在两个文件夹中,分别为bsp\stm32f10x\drivers和components\drivers\misc;
讲解函数rt_pin_mode
我们先来看看第一个函数rt_pin_mode:
void rt_pin_mode(rt_base_t pin, rt_base_t mode)
{
RT_ASSERT(_hw_pin.ops != RT_NULL);
_hw_pin.ops->pin_mode(&_hw_pin.parent, pin, mode);
}
该函数第一个参数是引脚的索引号,第二个参数是引脚模式。首先会断言判断_hw_pin.ops这个结构体是否有效,有效的情况下就设置引脚的模式。先来看看这个_hw_pin。
/* RT-Thread的PIN设备和相关操作*/
struct rt_device_pin
{
struct rt_device parent;
const struct rt_pin_ops *ops;
};
static struct rt_device_pin _hw_pin;
PIN设备同样是继承了struct rt_device父类属性,rt_device之前有介绍过,这里不累述。看看结构体struct 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);
};
这个ops结构体中定义了6个函数,注意看它们的传参,再回想一下我们之前学stm32的时候,在初学stm32的时候,有标准库和HAL库,我们在初始化一个IO口的时候,类似是这样的:
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_5);
但是我们在RT_thread操作系统上使用IO口,比如设置IO口模式,传参仅仅是一个IO口的索引号和模式,就初始化了一个IO口,妙呀。
那么我们接下来看看_hw_pin.ops的值是什么。定位到函数rt_device_pin_register:
int rt_device_pin_register(const char *name, const struct rt_pin_ops *ops, void *user_data)
{
_hw_pin.parent.type = RT_Device_Class_Miscellaneous;
_hw_pin.parent.rx_indicate = RT_NULL;
_hw_pin.parent.tx_complete = RT_NULL;
_hw_pin.parent.init = RT_NULL;
_hw_pin.parent.open = RT_NULL;
_hw_pin.parent.close = RT_NULL;
_hw_pin.parent.read = _pin_read;
_hw_pin.parent.write = _pin_write;
_hw_pin.parent.control = _pin_control;
#endif
_hw_pin.ops = ops;
_hw_pin.parent.user_data = user_data;
/* register a character device */
rt_device_register(&_hw_pin.parent, name, RT_DEVICE_FLAG_RDWR);
rt_device_open(&_hw_pin.parent, RT_DEVICE_FLAG_RDWR);
return 0;
}
可以看到_hw_pin.ops 的值是传参ops,那么我们继续看看函数rt_device_pin_register哪里使用了,定位到:
int rt_hw_pin_init(void)
{
#if defined(__HAL_RCC_GPIOA_CLK_ENABLE)
__HAL_RCC_GPIOA_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOB_CLK_ENABLE)
__HAL_RCC_GPIOB_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOC_CLK_ENABLE)
__HAL_RCC_GPIOC_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOD_CLK_ENABLE)
__HAL_RCC_GPIOD_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOE_CLK_ENABLE)
__HAL_RCC_GPIOE_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOF_CLK_ENABLE)
__HAL_RCC_GPIOF_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOG_CLK_ENABLE)
#ifdef SOC_SERIES_STM32L4
HAL_PWREx_EnableVddIO2();
#endif
__HAL_RCC_GPIOG_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOH_CLK_ENABLE)
__HAL_RCC_GPIOH_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOI_CLK_ENABLE)
__HAL_RCC_GPIOI_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOJ_CLK_ENABLE)
__HAL_RCC_GPIOJ_CLK_ENABLE();
#endif
#if defined(__HAL_RCC_GPIOK_CLK_ENABLE)
__HAL_RCC_GPIOK_CLK_ENABLE();
#endif
return rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL);
}
该函数前面都是对GPIO时钟使能,后面就调用了函数rt_device_pin_register,可以看到形参ops的值就是&_stm32_pin_ops,接下来我们继续看看_stm32_pin_ops到底是个啥。
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,
};
里面就是赋值了几个指向函数的指针变量。现在我们看看函数stm32_pin_mode:
static void stm32_pin_mode(rt_device_t dev, rt_base_t pin, rt_base_t mode)
{
const struct pin_index *index;
GPIO_InitTypeDef GPIO_InitStruct;
/*根据传参pin更新index*/
index = get_pin(pin);
if (index == RT_NULL)//如果无效直接返回
return;
/*初始化GPIO结构体*/
GPIO_InitStruct.Pin = index->pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;//GPIO速度默认都设置为非常快
/*模式设置*/
if (mode == PIN_MODE_OUTPUT)
{
/* output setting */
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
}
else if (mode == PIN_MODE_INPUT)
{
/* input setting: not pull. */
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
}
else if (mode == PIN_MODE_INPUT_PULLUP)
{
/* input setting: pull up. */
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
}
else if (mode == PIN_MODE_INPUT_PULLDOWN)
{
/* input setting: pull down. */
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
}
else if (mode == PIN_MODE_OUTPUT_OD)
{
/* output setting: od. */
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
}
/*完成初始化*/
HAL_GPIO_Init(index->gpio, &GPIO_InitStruct);
}
看上面的代码有两个新鲜的东西,一个是struct pin_index,另一个是函数get_pin。好吧,我们来看看这个struct pin_index:
/* STM32 GPIO driver */
struct pin_index
{
int index;
GPIO_TypeDef *gpio;
uint32_t pin;
};
其中,有常见的GPIO_TypeDef *,取值:GPIOA,GPIOB,…。
接着看看函数get_pin:
#define ITEM_NUM(items) sizeof(items) / sizeof(items[0])
static const struct pin_index *get_pin(uint8_t pin)
{
const struct pin_index *index;
if (pin < ITEM_NUM(pins))
{
index = &pins[pin];
if (index->index == -1)
index = RT_NULL;
}
else
{
index = RT_NULL;
}
return index;
};
宏ITEM_NUM(items)获取项数,比如一个数组uint32_t array[31],它的项数(个数)就是31。get_pin函数返回的是指向struct pin_index的指针,首先先判断输入的pin是不是在引脚最大个数范围内,如果是则index = &pins[pins],那么这个pins又是哪位呢,在drv_gpio.c中有定义pins,这里贴出一部分:
static const struct pin_index pins[] =
{
#if defined(GPIOA)
__STM32_PIN(0 , A, 0 ),
__STM32_PIN(1 , A, 1 ),
__STM32_PIN(2 , A, 2 ),
...
#endif
}
其中的__STM32_PIN()是一个宏:
#define __STM32_PIN(index, gpio, gpio_index) \
{ \
index, GPIO##gpio, GPIO_PIN_##gpio_index \
}
顺便提一下,里面的##是拼接符,将符号##两边的字符串拼接起来。现在看到这里,我们应该清楚的知道了__STM32_PIN()中的index就等于数组pins的元素下标值,我们get_pin(0)就返回了{ 0, GPIOA, GPIO_PIN_0 }。到此,已经了解函数rt_pin_mode的实现过程。
但是我们想想,我们是通过rt_base_t pin引脚编号让程序解析该编号对应的是哪个GPIO上的哪一个IO口,为什么不直接像标准库或者HAL库一样直接给出参数GPIOx和GPIO_PIN_x?这是因为RT_thread本来就是想软件和硬件驱动分层,提高代码中各个层的独立性,方便移植。
我们有两种方法获取引脚编号,一种是查看驱动文件drv_gpio.c中的pins,一种是使用宏定义,前者上面有提到,就是
__STM32_PIN的index。我们来看看后者:
#define __STM32_PORT(port) GPIO##port
#define GET_PIN(PORTx,PIN) (rt_base_t)((16 * ( ((rt_base_t)__STM32_PORT(PORTx) - (rt_base_t)GPIOA)/(0x0400UL) )) + PIN)
宏GET_PIN的传参是GPIO组加上引脚号,对于STM32上的GPIO,每个组占用0x400个字节的内存,每个组16个IO口,对于stm32MCU芯片,GPIO组最多可以使用16个IO口。那么GET_PIN返回的就是某个GPIO某个Pin下的索引号。下面举个例子使用下rt_pin_mode:
#define PIN_KEY0 GET_PIN(D, 10) // PD10: KEY0 --> KEY
rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT);
//或者查找驱动文件,直接写成这样
rt_pin_mode(58, PIN_MODE_INPUT);
显然,第一种写法比较直观。这样就完成了PIN的模式设置。
讲解函数rt_pin_write
函数原型如下:
void rt_pin_write(rt_base_t pin, rt_base_t value)
{
RT_ASSERT(_hw_pin.ops != RT_NULL);
_hw_pin.ops->pin_write(&_hw_pin.parent, pin, value);
}
经过上面讲解函数rt_pin_mode,我们这次直接跳到pin_write实际的函数上一探究竟:
static void stm32_pin_write(rt_device_t dev, rt_base_t pin, rt_base_t value)
{
const struct pin_index *index;
index = get_pin(pin);
if (index == RT_NULL)
{
return;
}
HAL_GPIO_WritePin(index->gpio, index->pin, (GPIO_PinState)value);
}
同样,函数也是仅仅通过IO口的索引号和值value实现引脚的电平切换。value可以取宏定义PIN_LOW和PIN_HIGH。
讲解函数rt_pin_read
直接跳到pin_read实际的函数上一探究竟:
static int stm32_pin_read(rt_device_t dev, rt_base_t pin)
{
int value;
const struct pin_index *index;
value = PIN_LOW;
index = get_pin(pin);
if (index == RT_NULL)
{
return value;
}
value = HAL_GPIO_ReadPin(index->gpio, index->pin);
return value;
}
讲解函数rt_pin_attach_irq
若要使用引脚的中断功能,可以使用函数rt_pin_attach_irq将某个引脚配置为某种中断触发模式并且绑定一个中断回调函数到对应引脚。
函数原型如下:
static rt_err_t stm32_pin_attach_irq(struct rt_device *device, rt_int32_t pin,
rt_uint32_t mode, void (*hdr)(void *args), void *args)
{
const struct pin_index *index;
rt_base_t level;
rt_int32_t irqindex = -1;
/*根据引脚编号获取引脚*/
index = get_pin(pin);
if (index == RT_NULL)
{
return RT_ENOSYS;
}
/*查找index->pin是几号管脚,GPIO_PIN_0对应0,GPIO_PIN_1对应1...正常情况下irqinde值范围为0~15*/
irqindex = bit2bitno(index->pin);
if (irqindex < 0 || irqindex >= ITEM_NUM(pin_irq_map))
{
return RT_ENOSYS;
}
/*失能中断*/
level = rt_hw_interrupt_disable();
/*看看引脚的中断配置是否需要更新*/
if (pin_irq_hdr_tab[irqindex].pin == pin &&
pin_irq_hdr_tab[irqindex].hdr == hdr &&
pin_irq_hdr_tab[irqindex].mode == mode &&
pin_irq_hdr_tab[irqindex].args == args)
{
/*如果不需要,就恢复(使能)中断*/
rt_hw_interrupt_enable(level);
return RT_EOK;
}
/*判断该中断线是否被其他的IO口占用了,如果是那么外部中断初始化失败返回*/
if (pin_irq_hdr_tab[irqindex].pin != -1)
{
rt_hw_interrupt_enable(level);
return RT_EBUSY;
}
/*更新*/
pin_irq_hdr_tab[irqindex].pin = pin;
pin_irq_hdr_tab[irqindex].hdr = hdr;
pin_irq_hdr_tab[irqindex].mode = mode;
pin_irq_hdr_tab[irqindex].args = args;
rt_hw_interrupt_enable(level);
return RT_EOK;
}
模式可选如下:
#define PIN_IRQ_MODE_RISING 0x00 /* 上 升 沿 触 发 */
#define PIN_IRQ_MODE_FALLING 0x01 /* 下 降 沿 触 发 */
#define PIN_IRQ_MODE_RISING_FALLING 0x02 /* 边 沿 触 发 (上 升 沿 和 下 降 沿 都 触 发) */
#define PIN_IRQ_MODE_HIGH_LEVEL 0x03 /* 高 电 平 触 发 */
#define PIN_IRQ_MODE_LOW_LEVEL 0x04 /* 低 电 平 触 发 */
参数hdr是中断回调函数,用户需要自行定义这个函数,args是中断回调函数中的参数,不需要时设置为RT_NULL。在函数stm32_pin_attach_irq中多次看到pin_irq_hdr_tab,这个的定义以及初始化是这样子的:
static struct rt_pin_irq_hdr pin_irq_hdr_tab[16] =
{
{-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},
};
总共有16个元素,默认中断线没有连接IO口,模式无效,中断回调函数和函数参数不存在。当然,上面的函数stm32_pin_attach_irq仅仅是更新了pin_irq_hdr_tab,将IO口与irq绑定而已,如果想触发中断并且执行中断函数,还需要写上函数rt_pin_irq_enable。
讲解函数rt_pin_irq_enable
函数rt_pin_irq_enable的作用是使能或者失能引脚的外部中断功能。其最终的原型是:
static rt_err_t stm32_pin_irq_enable(struct rt_device *device, rt_base_t pin,
rt_uint32_t enabled)
{
const struct pin_index *index;
const struct pin_irq_map *irqmap;
rt_base_t level;
rt_int32_t irqindex = -1;
GPIO_InitTypeDef GPIO_InitStruct;
/*获取哪个GPIO,哪个Pin*/
index = 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(pin_irq_map))
{
return RT_ENOSYS;
}
level = rt_hw_interrupt_disable();
if (pin_irq_hdr_tab[irqindex].pin == -1)
{
rt_hw_interrupt_enable(level);
return RT_ENOSYS;
}
/*得到外部中断中IO口和IRQn中断函数的映射关系*/
irqmap = &pin_irq_map[irqindex];
/* 配置底层引脚 */
GPIO_InitStruct.Pin = index->pin;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
switch (pin_irq_hdr_tab[irqindex].mode)
{
case PIN_IRQ_MODE_RISING:
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
break;
case PIN_IRQ_MODE_FALLING:
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
break;
case PIN_IRQ_MODE_RISING_FALLING:
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;
break;
}
HAL_GPIO_Init(index->gpio, &GPIO_InitStruct);
/*默认中断引脚的中断优先级都是抢占5,响应0*/
HAL_NVIC_SetPriority(irqmap->irqno, 5, 0);
/*使能中断*/
HAL_NVIC_EnableIRQ(irqmap->irqno);
pin_irq_enable_mask |= irqmap->pinbit;
rt_hw_interrupt_enable(level);
}
/*如果是失能外部中断*/
else if (enabled == PIN_IRQ_DISABLE)
{
irqmap = get_pin_irq_map(index->pin);
if (irqmap == RT_NULL)
{
return RT_ENOSYS;
}
level = rt_hw_interrupt_disable();
HAL_GPIO_DeInit(index->gpio, index->pin);
/*解除中断功能*/
pin_irq_enable_mask &= ~irqmap->pinbit;
if (( irqmap->pinbit>=GPIO_PIN_5 )&&( irqmap->pinbit<=GPIO_PIN_9 ))
{
if(!(pin_irq_enable_mask&(GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9)))
{
HAL_NVIC_DisableIRQ(irqmap->irqno);
}
}
else if (( irqmap->pinbit>=GPIO_PIN_10 )&&( irqmap->pinbit<=GPIO_PIN_15 ))
{
if(!(pin_irq_enable_mask&(GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15)))
{
HAL_NVIC_DisableIRQ(irqmap->irqno);
}
}
else
{
HAL_NVIC_DisableIRQ(irqmap->irqno);
}
rt_hw_interrupt_enable(level);
}
else
{
return -RT_ENOSYS;
}
return RT_EOK;
}
上面遇到变量pin_irq_map的结构体定义是这样的:
struct pin_irq_map
{
rt_uint16_t pinbit;
IRQn_Type irqno;
};
结构体里定义某个引脚,16个位表示最多16个引脚;定义IRQn_Type类型的变量,这个是中断号。
static const struct pin_irq_map pin_irq_map[] =
{
{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},
};
讲解函数rt_pin_detach_irq
函数的最终本体是:
static rt_err_t stm32_pin_dettach_irq(struct rt_device *device, rt_int32_t pin)
{
const struct pin_index *index;
rt_base_t level;
rt_int32_t irqindex = -1;
index = get_pin(pin);
if (index == RT_NULL)
{
return RT_ENOSYS;
}
irqindex = bit2bitno(index->pin);
if (irqindex < 0 || irqindex >= ITEM_NUM(pin_irq_map))
{
return RT_ENOSYS;
}
level = rt_hw_interrupt_disable();
/*如果引脚已经脱离了与中断的关系,就直接返回成功*/
if (pin_irq_hdr_tab[irqindex].pin == -1)
{
rt_hw_interrupt_enable(level);
return RT_EOK;
}
/*如果没脱离,那么就在此脱离*/
pin_irq_hdr_tab[irqindex].pin = -1;
pin_irq_hdr_tab[irqindex].hdr = RT_NULL;
pin_irq_hdr_tab[irqindex].mode = 0;
pin_irq_hdr_tab[irqindex].args = RT_NULL;
rt_hw_interrupt_enable(level);
return RT_EOK;
}
该函数也是仅仅改变表pin_irq_hdr_tab的内容。
将一个按键IO口设置成外部中断:
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
void key1_interrupt(void *args)
{
if(rt_pin_read(PIN_KEY1) == 0)
{
LOG_D("Key1 has pressed!\r\n");
}
}
int main(void)
{
/* 设置 KEY0 引脚的模式为输入模式 */
rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT);
rt_pin_attach_irq(PIN_KEY1, PIN_IRQ_MODE_FALLING , key1_interrupt, RT_NULL);
rt_pin_irq_enable(PIN_KEY1, PIN_IRQ_ENABLE);
while (1)
{
rt_thread_mdelay(10);
}
return 0;
}