RT-Thread PIN设备
- RT-Thread PIN设备驱动框架
- RT-Thread PIN设备驱动层次图
- RT-Thread PIN设备注册
- RT-Thread PIN设备注册函数
- RT-Thread PIN设备操作函数
- pin_get
- pin_mode
- pin_write
- pin_read
- pin_attach_irq
- pin_detach_irq
- pin_irq_enable
PIN设备又叫GPIO设备,是MCU输入输出的一种设备,RT-Thread将GPIO抽象成PIN设备,以实现对GPIO的基本操作。
比如上面两个GPIO设备,其中一个作为输出控制LED灯的亮灭,一个作为输入判断按键的高低电平。
对于GPIO的操作都有以下几点:
- 设置GPIO的方向,是作为输入还是输出
- 设置GPIO的属性,是上拉,下拉,还是推挽、开漏等
- 设置GPIO的高低电平或者读取GPIO的电平
- 如果需要中断,还需要设置GPIO的中断
- 有些GPIO能复用成其他外设功能,比如ADC、UART等
RT-Thread的PIN设备框架就需要实现上面的GPIO基本功能。
RT-Thread PIN设备驱动框架
RT-Thread PIN设备驱动层次图
RT-Thread PIN设备注册
RT-Thread PIN设备注册函数
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;
#ifdef RT_USING_DEVICE_OPS
_hw_pin.parent.ops = &pin_ops;
#else
_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; // PIN设备 操作函数
_hw_pin.parent.user_data = user_data;
/* register a character device */
rt_device_register(&_hw_pin.parent, name, RT_DEVICE_FLAG_RDWR); // 注册PIN设备
return 0;
}
PIN设备的关键是用户的OPS操作函数
RT-Thread PIN设备操作函数
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);
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);
rt_base_t (*pin_get)(const char *name);
};
- pin_mode:设置GPIO的模式,比如上拉下拉。
- pin_write:设置GPIO的电平状态。
- pin_read:读取GPIO的电平。
- pin_attach_irq:绑定GPIO中断。
- pin_detach_irq:脱离GPIO中断。
- pin_irq_enable:GPIO中断使能。
- pin_get:获取GPIO编号。
注意事项:引脚编号
对于MCU厂商来说,会给自家MCU的GPIO进行一个分类,比如STM32的MCU就会以PAX、PBX等名称进行分类,而像NXP的就会以GPIO1_IOX、GPIO2_IOX等名称进行分类。RT-Thread的PIN设备框架为了能做到通用,统一使用了引脚编号这个属性,不管MCU的引脚怎么命名,在RT-Thread里面都是以0、1、2、3等数字进行操作。
pin_get
pin_get函数的作用是获取MCU的引脚编号。下面以STM32为例进行说明
#define PIN_NUM(port, no) (((((port)&0xFu) << 4) | ((no)&0xFu)))
/* e.g. PE.7 */
static rt_base_t stm32_pin_get(const char *name)
{
rt_base_t pin = 0;
int hw_port_num, hw_pin_num = 0;
int i, name_len;
name_len = rt_strlen(name);
if ((name_len < 4) || (name_len >= 6)) // 判断传入的引脚名字长度是否非法
{
goto out;
}
if ((name[0] != 'P') || (name[2] != '.')) // 判断传入的引脚名字的第0和第2位是否非法
{
goto out;
}
if ((name[1] >= 'A') && (name[1] <= 'Z')) // 判断传入的引脚范围是否非法
{
hw_port_num = (int)(name[1] - 'A');
}
else
{
goto out;
}
for (i = 3; i < name_len; i++) // 计算引脚编号
{
hw_pin_num *= 10;
hw_pin_num += name[i] - '0';
}
pin = PIN_NUM(hw_port_num, hw_pin_num); // 返回引脚编号
return pin;
out:
rt_kprintf("Px.y x:A~Z y:0-15, e.g. PA.0\n");
return -RT_EINVAL;
}
比如传入的引脚名字为PB.0,那么通过计算可以得到PB.0的引脚编号为16。不同的MCU计算方法可能会不一样,但是道理都是一样的,就是将引脚名称全部转化为引脚编号。
pin_mode
pin_mode作用是设置GPIO引脚的模式
/*
device :设备句柄
pin :引脚编号
mode :引脚模式
*/
void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_base_t mode);
RT-Thread提供以下几种GPIO的引脚模式
模式 | 说明 |
---|---|
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_uint8_t mode)
{
GPIO_InitTypeDef GPIO_InitStruct;
if (PIN_PORT(pin) >= PIN_STPORT_MAX) // 判断输入的引脚编号是否非法
{
return;
}
/* Configure GPIO_InitStructure */
GPIO_InitStruct.Pin = PIN_STPIN(pin);
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
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(PIN_STPORT(pin), &GPIO_InitStruct); // 配置GPIO
}
如果MCU还有其他的模式配置可以自行添加
pin_write
pin_write作用是设置GPIO引脚的电平状态
/*
device :设备句柄
pin :引脚编号
value :输出电平
*/
void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_base_t value);
RT-Thread提供以下的电平
电平状态 | 说明 |
---|---|
PIN_LOW | 低电平 |
PIN_HIGH | 高电平 |
static void stm32_pin_write(rt_device_t dev, rt_base_t pin, rt_uint8_t value)
{
GPIO_TypeDef *gpio_port;
uint16_t gpio_pin;
if (PIN_PORT(pin) < PIN_STPORT_MAX) // 判断输入的引脚编号是否非法
{
gpio_port = PIN_STPORT(pin);
gpio_pin = PIN_STPIN(pin);
HAL_GPIO_WritePin(gpio_port, gpio_pin, (GPIO_PinState)value); // 设置GPIO电平
}
}
pin_read
pin_read作用是读取GPIO引脚的电平状态
/*
device :设备句柄
pin :引脚编号
返回值: GPIO电平
*/
int (*pin_read)(struct rt_device *device, rt_base_t pin);
电平状态 | 说明 |
---|---|
PIN_LOW | 低电平 |
PIN_HIGH | 高电平 |
static rt_ssize_t stm32_pin_read(rt_device_t dev, rt_base_t pin)
{
GPIO_TypeDef *gpio_port;
uint16_t gpio_pin;
GPIO_PinState state = GPIO_PIN_RESET;
if (PIN_PORT(pin) < PIN_STPORT_MAX) // 判断输入的引脚编号是否非法
{
gpio_port = PIN_STPORT(pin);
gpio_pin = PIN_STPIN(pin);
state = HAL_GPIO_ReadPin(gpio_port, gpio_pin); // 读取GPIO电平
}
else
{
return -RT_EINVAL;
}
return (state == GPIO_PIN_RESET) ? PIN_LOW : PIN_HIGH; // 返回高或低
}
pin_attach_irq
pin_attach_irq作用是绑定GPIO引脚中断
/*
device :设备句柄
pin :引脚编号
mode :引脚中断类型
hdr :引脚中断函数
args :中断函数参数
返回值:错误代码
*/
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-Thread提供以下的引脚触发类型
触发类型 | 说明 |
---|---|
PIN_IRQ_MODE_RISING | 上升沿触发 |
PIN_IRQ_MODE_FALLING | 下升沿触发 |
PIN_IRQ_MODE_RISING_FALLING | 双边沿触发 |
PIN_IRQ_MODE_HIGH_LEVEL | 高电平触发 |
PIN_IRQ_MODE_LOW_LEVEL | 低电平触发 |
引脚的中断绑定函数需要在程序中进行保存,所以可以建一张中断表保存引脚的中断函数,当中断来临的时候,再通过查表的方式进行调用。下面以STM32为例进行说明
// 引脚的中断表
static struct rt_pin_irq_hdr pin_irq_hdr_tab[] =
{
{-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},
};
static rt_err_t stm32_pin_attach_irq(struct rt_device *device, rt_base_t pin,
rt_uint8_t mode, void (*hdr)(void *args), void *args)
{
rt_base_t level;
rt_int32_t irqindex = -1;
if (PIN_PORT(pin) >= PIN_STPORT_MAX) // 判断引脚编号是否非法
{
return -RT_ENOSYS;
}
irqindex = bit2bitno(PIN_STPIN(pin)); // 通过引脚编号反推得到中断号
if (irqindex < 0 || irqindex >= (rt_int32_t)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;
}
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;
}
rt_inline void pin_irq_hdr(int irqno)
{
if (pin_irq_hdr_tab[irqno].hdr) // 通过查表执行中断函数
{
pin_irq_hdr_tab[irqno].hdr(pin_irq_hdr_tab[irqno].args);
}
}
上面的函数通过引脚编号得到相应的引脚中断号,然后将引脚的中断函数注册进中断表,当中断来临的时候通过pin_irq_hdr函数在中断表找到相应的中断函数然执行。
pin_detach_irq
pin_detach_irq作用是解绑GPIO引脚中断
/*
device :设备句柄
pin :引脚编号
返回值: 错误代码
*/
rt_err_t (*pin_detach_irq)(struct rt_device *device, rt_int32_t pin);
下面以STM32为例进行说明
static rt_err_t stm32_pin_dettach_irq(struct rt_device *device, rt_base_t pin)
{
rt_base_t level;
rt_int32_t irqindex = -1;
if (PIN_PORT(pin) >= PIN_STPORT_MAX) // 判断引脚编号是否非法
{
return -RT_ENOSYS;
}
irqindex = bit2bitno(PIN_STPIN(pin));
if (irqindex < 0 || irqindex >= (rt_int32_t)ITEM_NUM(pin_irq_map)) // 判断中断号是否非法
{
return -RT_ENOSYS;
}
level = rt_hw_interrupt_disable(); // 关闭中断
if (pin_irq_hdr_tab[irqindex].pin == -1) // GPIO引脚中断没有中断
{
rt_hw_interrupt_enable(level);
return RT_EOK;
}
// 将GPIO中断号对应的中断表索引属性清空
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_enable
pin_irq_enable作用是使能GPIO引脚中断
/*
device :设备句柄
pin :引脚编号
enabled: 使能
返回值: 错误代码
*/
rt_err_t (*pin_irq_enable)(struct rt_device *device, rt_base_t pin, rt_uint32_t enabled);
以下以STM32为例
static rt_err_t stm32_pin_irq_enable(struct rt_device *device, rt_base_t pin,
rt_uint8_t enabled)
{
const struct pin_irq_map *irqmap;
rt_base_t level;
rt_int32_t irqindex = -1;
GPIO_InitTypeDef GPIO_InitStruct;
if (PIN_PORT(pin) >= PIN_STPORT_MAX) // 判断引脚编号是否非法
{
return -RT_ENOSYS;
}
if (enabled == PIN_IRQ_ENABLE) // 如果引脚中断使能
{
irqindex = bit2bitno(PIN_STPIN(pin));
if (irqindex < 0 || irqindex >= (rt_int32_t)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;
}
irqmap = &pin_irq_map[irqindex];
/* Configure GPIO_InitStructure */
GPIO_InitStruct.Pin = PIN_STPIN(pin);
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
switch (pin_irq_hdr_tab[irqindex].mode) // 设置GPIO中断模式
{
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(PIN_STPORT(pin), &GPIO_InitStruct);
// 使能GPIO中断
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(PIN_STPIN(pin));
if (irqmap == RT_NULL)
{
return -RT_ENOSYS;
}
level = rt_hw_interrupt_disable();
HAL_GPIO_DeInit(PIN_STPORT(pin), PIN_STPIN(pin));
pin_irq_enable_mask &= ~irqmap->pinbit;
#if defined(SOC_SERIES_STM32F0) || defined(SOC_SERIES_STM32G0)
if ((irqmap->pinbit >= GPIO_PIN_0) && (irqmap->pinbit <= GPIO_PIN_1))
{
if (!(pin_irq_enable_mask & (GPIO_PIN_0 | GPIO_PIN_1)))
{
HAL_NVIC_DisableIRQ(irqmap->irqno);
}
}
else if ((irqmap->pinbit >= GPIO_PIN_2) && (irqmap->pinbit <= GPIO_PIN_3))
{
if (!(pin_irq_enable_mask & (GPIO_PIN_2 | GPIO_PIN_3)))
{
HAL_NVIC_DisableIRQ(irqmap->irqno);
}
}
else if ((irqmap->pinbit >= GPIO_PIN_4) && (irqmap->pinbit <= GPIO_PIN_15))
{
if (!(pin_irq_enable_mask & (GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 |
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);
}
#else
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);
}
#endif
rt_hw_interrupt_enable(level);
}
else
{
return -RT_ENOSYS;
}
return RT_EOK;
}