开启攻城狮的成长之旅!这是我参与的由 CSDN博客专家 架构师李肯和 瑞萨MCU 联合发起的「 致敬未来的攻城狮计划 」的第2天,点击查看活动计划详情
首先非常感谢李老师能给我参加这个计划的机会,让我有机会接触到许多的开发板,同时也感谢瑞萨官方 为我们提供的开发板。在参加活动的期间,结识了许多的大佬,让我收获了许多的知识,我感觉短短的几天,收获的知识与开发经验比自己独自学习几个月的收获还要多。所以,希望各位大佬们也能加入李老师的这个活动,而且,李老师水平真的很高,而且非常耐心,谁来谁知道!!!!
好了,接下来我们开始进入今天的主题,如何在RA2E1上基于RT-Thread优雅地使用PIN设备!
本文将会详细介绍有关RT-Thread的PIN设备,但是考虑到可能有部分和我一样的初学者,所以有些具体细节并未介绍。其实本文中的内容并不仅仅适用于一款开发板,只要运行了RT-Thread操作系统的开发板,都是一样的。
PIN设备也叫做GPIO设备(General Purpose Input Output,通用输入 / 输出),在后文中可能会混着使用,这一点事先声明以下,避免造成读者的混乱。
GPIO,既然叫通用输入 / 输出设备,那么肯定是和输入输出有关的,接下来我们就来简单介绍一下PIN设备的输入输出模式。
输出模式一般包括:推挽、开漏、上拉、下拉这四种模式。当引脚为输出模式时,我们可以通过配置引脚输出的电平状态来控制连接的设备,比如我们的LED灯。
输入模式一般包括:浮空、上拉、下拉、模拟。当引脚为输入模式时,我们可以读取引脚的电平状态,特别是模拟,这部分在ADC的时候会用到。
本文也会简单介绍一下有关中断的知识,具体的中断模式如下图所示(官方文档):
在RT-Thread中,应用程序可以通过PIN设备来操作GPIO,接下来我们来看看PIN设备的模型,他是从设备对象派生而来的,当然,这部分我们只会简答介绍一下,不会过多介绍。
/* pin device and operations for RT-Thread */
struct rt_device_pin
{
struct rt_device parent; /* 设备基类 */
const struct rt_pin_ops *ops; /* PIN设备驱动方法 */
};
我们主要的就是需要去实现这些驱动方法,我们在下面会介绍。
接下来,我们需要来创建一个PIN设备,而主要需要做的就是实现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_get | 获取某个PIN的引脚编号 |
pin_mode | 设置引脚模式,将某个引脚初始化成相应的模式 |
pin_write | 设置某个引脚的输出电平 |
pin_read | 读取某个引脚的电平 |
pin_attach_irq | 中断操作,为某个绑定引脚中断回调函数 |
pin_detach_irq | 脱离某个引脚的中断回调函数 |
pin_irq_enable | 开启或关闭引脚中断 |
之后我们再使用以下的接口注册到PIN设备驱动框架中,其中参数和返回值我已给出。
int rt_device_pin_register ( const char * name,
const struct rt_pin_ops * ops,
void * user_data
)
参数 | 描述 |
---|---|
name | PIN设备名称 |
ops | PIN设备操作方法对象指针 |
user_data | 用户数据,一般设为RT_NULL |
返回 | 描述 |
---|---|
RT_EOK | 注册成功 |
-RT_ERROR | 注册失败,已有其他驱动使用该name注册。 |
接下来我们就需要来访问我们的PIN设备了,主要就是通过PIN设备管理接口去调用我们刚刚实现的PIN设备的操作方法,但是在一般的开发中,PIN设备的操作方法一般都是官方帮我们写好了(只是可能名称有点不一样,比如瑞萨的就是ra_开头的,我一开始看成了rt)我们只需要调用函数即可。接下来我们就来详细介绍一下。
RT-Thread 提供的引脚编号需要和芯片的引脚号区分开来,它们并不是同一个概念,引脚编号由 PIN 设备驱动程序定义,和具体的芯片相关。有3种方式可以获取引脚编号: API 接口获取、使用宏定义和查看PIN 驱动文件,接下来我们来介绍一下。
这个就是使用 rt_pin_get() 获取引脚编号,如下获取 P502 的引脚编号:
rt_base_t led1_pin = rt_pin_get(LED1_PIN); /* 获取 P502 的引脚编号 */
我们的开发板就是通过这种方式,所以我们只介绍这种。
引脚在使用前需要先设置好输入或者输出模式,通过如下函数完成:
void rt_pin_mode(rt_base_t pin, rt_base_t mode);
参数 | 描述 |
---|---|
pin | 引脚编号 |
mode | 引脚工作模式 |
目前 RT-Thread 支持的引脚工作模式可取以下 5 种宏定义值之一,每种模式对应的芯片实际支持的模式需参考 PIN 设备驱动程序的具体实现:
#define PIN_MODE_OUTPUT 0x00 /* 输出 */
#define PIN_MODE_INPUT 0x01 /* 输入 */
#define PIN_MODE_INPUT_PULLUP 0x02 /* 上拉输入 */
#define PIN_MODE_INPUT_PULLDOWN 0x03 /* 下拉输入 */
#define PIN_MODE_OUTPUT_OD 0x04 /* 开漏输出 */
使用示例如下所示:
#define LED1_PIN "P502" /* Onboard LED pins */
/* 获取引脚编号 */
rt_base_t led1_pin = rt_pin_get(LED1_PIN);
/* 引脚为输出模式 */
rt_pin_mode(led1_pin, PIN_MODE_OUTPUT);
设置引脚输出电平的函数如下所示:
void rt_pin_write(rt_base_t pin, rt_base_t value);
参数 | 描述 |
---|---|
pin | 引脚编号 |
value | 电平逻辑值,可取 2 种宏定义值之一:PIN_LOW (低电平),PIN_HIGH( 高电平) |
使用示例如下所示:
#define LED1_PIN "P502" /* Onboard LED pins */
/* 获取引脚编号 */
rt_base_t led1_pin = rt_pin_get(LED1_PIN);
/* 引脚为输出模式 */
rt_pin_mode(led1_pin, PIN_MODE_OUTPUT);
/* 设置低电平 */
rt_pin_write(led1_pin, PIN_HIGH);
读取引脚电平的函数如下所示:
int rt_pin_read(rt_base_t pin);
参数 | 描述 |
---|---|
pin | 引脚编号 |
返回 | 描述 |
PIN_LOW | 低电平 |
PIN_HIGH | 高电平 |
使用示例如下所示:
#define LED1_PIN "P502" /* Onboard LED pins */
int status;
/* 获取引脚编号 */
rt_base_t led1_pin = rt_pin_get(LED1_PIN);
/* 引脚为输出模式 */
rt_pin_mode(led1_pin, PIN_MODE_OUTPUT);
/* 设置低电平 */
rt_pin_write(led1_pin, PIN_HIGH);
status = rt_pin_read(led1_pin);
因为介绍中断较少,这边就简单介绍一下基本函数,具体实现在代码体现。
若要使用到引脚的中断功能,可以使用如下函数将某个引脚配置为某种中断触发模式并绑定一个中断回调函数到对应引脚,当引脚中断发生时,就会执行回调函数:
rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode,
void (*hdr)(void *args), void *args);
参数 | 描述 |
---|---|
pin | 引脚编号 |
mode | 中断触发模式 |
hdr | 中断回调函数,用户需要自行定义这个函数 |
args | 中断回调函数的参数,不需要时设置为 RT_NULL |
返回 | 描述 |
RT_EOK | 绑定成功 |
错误码 | 绑定失败 |
中断触发模式 mode 可取如下 5 种宏定义值之一:
#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 /* 低电平触发 */
绑定好引脚中断回调函数后使用下面的函数使能引脚中断:
rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled);
参数 | 描述 |
---|---|
pin | 引脚编号 |
enabled | 状态,可取 2 种值之一:PIN_IRQ_ENABLE(开启),PIN_IRQ_DISABLE(关闭) |
返回 | 描述 |
RT_EOK | 使能成功 |
错误码 | 使能失败 |
可以使用如下函数脱离引脚中断回调函数:
rt_err_t rt_pin_detach_irq(rt_int32_t pin);
参数 | 描述 |
---|---|
pin | 引脚编号 |
返回 | 描述 |
RT_EOK | 脱离成功 |
错误码 | 脱离失败 |
引脚脱离了中断回调函数以后,中断并没有关闭,还可以调用绑定中断回调函数再次绑定其他回调函数。
对具体的实现感兴趣的同学可以自己去查看一下具体的实现方法,都在drv_gpio.c文件里面。在这边碍于篇幅过长,就不一一介绍了。
接下来,我们就看一下我们的代码,因为没有板载蜂鸣器,我就自己接了一个在P105引脚上(J1-11),实验现象就是当我们按下复位键(RESTE)后,灯亮的时候蜂鸣器刚好会亮一下,然后在FinSH控制台可以调用我们自己写的msh命令,在此之后,按下板载按键,LED2就会翻转。
#include
#include "hal_data.h"
#include
#define LED1_PIN "P502" /* Onboard LED1 pins */
#define LED2_PIN "P501" /* Onboard LED2 pins */
#define BEEP_PIN "P105" /* 蜂鸣器 */
#define KEY_INPUT "P004"/* 按键 */
void hal_entry(void)
{
rt_kprintf("\nHello RT-Thread!\n");
/* 获取引脚编号 */
rt_base_t led1_pin = rt_pin_get(LED1_PIN);
rt_base_t beep_pin = rt_pin_get(BEEP_PIN);
while (1)
{
/* 写高低电平,驱动灯和蜂鸣器 */
rt_pin_write(led1_pin, PIN_HIGH);
rt_pin_write(beep_pin, PIN_LOW);
/* 延时 */
rt_thread_mdelay(500);
/* 写高低电平,驱动灯和蜂鸣器 */
rt_pin_write(led1_pin, PIN_LOW);
rt_pin_write(beep_pin, PIN_HIGH);
/* 延时 */
rt_thread_mdelay(500);
}
}
/* 回调函数,LED2翻转 */
void irq_callback_test(void *args)
{
rt_base_t led2_pin = rt_pin_get(LED2_PIN);
rt_pin_write(led2_pin, !rt_pin_read(led2_pin));
}
void icu_sample(void)
{
/* init */
/* 获取引脚编号 */
rt_base_t pin = rt_pin_get(KEY_INPUT);
rt_kprintf("\n pin : 0x%04X \n", pin);
/* 绑定引脚中断回调函数 */
rt_err_t err = rt_pin_attach_irq(pin, PIN_IRQ_MODE_RISING,
irq_callback_test, RT_NULL);
if (RT_EOK != err)
{
rt_kprintf("\n attach irq failed. \n");
}
/* 使能引脚中断 */
err = rt_pin_irq_enable(pin, PIN_IRQ_ENABLE);
if (RT_EOK != err)
{
rt_kprintf("\n enable irq failed. \n");
}
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(icu_sample, icu sample);
现象如下所示:
RT_PIN
这篇文章呢,主要就是用来记录一下有关RT-Thread的PIN设备。因为在此之前,我感觉应该是差不多理解了PIN设备,但是当我去深究实现的时候,我才发现没那么简单,我还是有很多不明白的地方。比如在drv_gpio.c文件中,官方说有一个PIN驱动文件,但是我一直没找到,后来才知道:原来不是所有的BSP都有这个文件的,害得我一顿好找,哈哈哈。
还有一个巨大的收获就是,知道了实时操作系统里面中断不能加延时函数,比如下面这样:
/* 回调函数,LED2翻转 */
void irq_callback_test(void *args)
{
/* 被禁止,会直接退出程序!! */
rt_thread_mdelay(500);
rt_base_t led2_pin = rt_pin_get(LED2_PIN);
rt_pin_write(led2_pin, !rt_pin_read(led2_pin));
}
后来我才明白:中断里面一般要求快进快出,因为在中断时间过长,可能会影响其他部分的正常运行。所以RTOS编程思维与裸机差异较大,以后得转变思想了。然后按键部分就没做消抖处理,因为涉及到定时器,但是本文主要介绍PIN设备,就不展开将定时器了,省的主题乱了,这部分以后会详细介绍。