讲完uart设备之后,我们已经熟悉rt-thread i/o 设备模型了,回头看看基本的 pin 设备。 目录 前言
一、pin 设备模型解析
1.1 初识 gpio 操作函数
1.2 pin 设备框架
1.3 pin 设备驱动框架层
实现的函数
pin 设备控制块
注册函数
1.4 pin 设备驱动层
实现的函数
初始化函数
☆引脚定义☆
二、pin 设备操作函数
2.1 获取 pin 索引号方法
2.2 操作函数
2.2.1 设置 gpio 模式
2.2.2 设置/ 读取 gpio 电平
2.2.3 绑定/脱离中断回调函数
2.2.4 使能中断
三、pin 设备示例
结语
前言 我们学习一个 mcu 的最基本的 gpio 口,也就是 pin 设备模型,我们还没有讲过,至于原因之前也说了,因为 pin 设备的操作函数与我们介绍的 i/o 设备模型的通用函数名称不太对应,对于新手来说先将 pin 设备可能会让人难以理解。
所以前面的文章我们先讲了 uart 设备模型,从源码分析了一下 uart 设备的设计思路,从设备驱动层,和设备驱动框架层再到 i/o 设备管理层,最后到应用层,我们都理过一遍。
有了前面的经验,本文我们就来学习了解 rt-thread pin设备 。
❤️
本 rt-thread 专栏记录的开发环境:
rt-thread记录(一、rt-thread 版本、rt-thread studio开发环境 及 配合cubemx开发快速上手)
rt-thread记录(二、rt-thread内核启动流程 — 启动文件和源码分析)
❤️
rt-thread 设备篇系列博文链接:
rt-thread记录(十、全面认识 rt-thread i/o 设备模型)
rt-thread记录(十一、i/o 设备模型之uart设备 — 源码解析)
rt-thread记录(十二、i/o 设备模型之uart设备 — 使用测试)
一、pin 设备模型解析 一直说到 pin 设备有点特殊,和我们讲 i/o 设备模型时候的设备感觉有一点区别的,那么到底怎么个特殊法?我们还是需要具体来分析一下:
1.1 初识 gpio 操作函数 我们还是从上层的 i/o 设备管理层来开始,看看 pin 设备管理层提供的访问 gpio 的接口有哪些:
我们可以发现,上面的 pin 设备管理接口的操作函数,与我们将的通用的函数完全不一样,如下图:
这也是为什么我们将设备示例的时候没有先讲 pin 设备的原因,怕很多小伙伴刚开始不理解,那么为什么会这样呢?
1.2 pin 设备框架 我们通过前面的 uart 设备的分析,已经知道了设备的基本的框架了,首先我们来看一下 上一篇文章讲到的 uart 设备框架:
对于 pin 设备来说,框架总结如下图表:
❤️ 前面一直说 pin 设备有点特别,那只不过是因为官方说明中 应用程序调用的不是 i/o 设备管理层的接口函数,而是直接调用的 pin 设备驱动框架层的接口函数:
知道了这一点的话,其实我们都不需要进行过多的分析,具体的过程分析可以查看前面几篇博文,我们这里只需要对 pin 设备驱动框架层 和 设备驱动层的接口简单的了解一下,毕竟 gpio 的操作还是很简单的。
1.3 pin 设备驱动框架层 通过上面的说明,我们知道 pin 设备的使用是直接调用的 设备驱动框架层的接口,所以我们来看看 pin 设备驱动框架层的文件(pin.c)有哪些函数接口:
实现的函数//私有的static struct rt_device_pin _hw_pin;static rt_size_t _pin_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)static rt_size_t _pin_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)static rt_err_t _pin_control(rt_device_t dev, int cmd, void *args)//可调用的int rt_device_pin_register(const char *name, const struct rt_pin_ops *ops, void *user_data);/* get pin number by name,such as pa.0,p0.12 */rt_base_t rt_pin_get(const char *name);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);
挑一个函数简单看看:
/* rt-thread hardware pin apis */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这个结构体是否有效,有效的情况下就设置引脚的模式。
第一个参数是引脚的索引号(这个在我们下面讲解 pin 设备驱动层的时候会有说明什么是索引号),
第二个参数是引脚模式(具体的模式我们也会再下面讲解gpio 设置时候统一说明)。
pin 设备控制块 在 rt-thread 中 pin 设备作为一个对象,那么肯定有他的对象控制块,和我们前面学习的所有的对象一样,在pin.h中有 pin 设备的对象结构体:
/* pin device and operations for rt-thread */struct rt_device_pin{ struct rt_device parent; // rt_device 我们前面讲过的,所有 device 的父类 const struct rt_pin_ops *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); rt_base_t (*pin_get)(const char *name);};
❤️ pin 设备的访问函数都是在 pin 设备控制块中的结构体成员 ops 中实现的,也是通过这个结构体成员与 底层驱动层关联起来 —— 在设备驱动层定义rt_pin_ops类型的变量,实现这些操作函数。
注册函数 在 pin设备初始化的时候,rt_hw_pin_init()会调用 rt_device_pin_register 函数进行 pin 设备的初始化。
pin 设备注册函数,使用这个注册函数,可以绑定底层驱动层的函数,也同时将设备接口提供给上层 i/o 设备管理层:
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; //pin 设备不需要 _hw_pin.parent.open = rt_null; // _hw_pin.parent.close = rt_null; // _hw_pin.parent.read = _pin_read; //* 把设备的read操作绑定在pin.c的_pin_read函数 */ _hw_pin.parent.write = _pin_write; //同上 _hw_pin.parent.control = _pin_control; //同上#endif /* 把drv_gpio.c所实现的_stm32_pin_ops绑定在_hw_pin.ops上 因为 pin 设备驱动层使用的注册函数为: rt_device_pin_register(pin, &_stm32_pin_ops, rt_null); */ _hw_pin.ops = ops; _hw_pin.parent.user_data = user_data; /* register a character device /* 将其注册进device设备框架中 */ */ rt_device_register(&_hw_pin.parent, name, rt_device_flag_rdwr); return 0;}
在注册函数中:_hw_pin.ops = ops; 这个操作就把设备驱动层实现的硬件操作函数给关联到了 设备驱动框架层。
官方说明的 pin 设备访问的接口就是在 设备驱动框架层 提供的函数接口。
但是我们看到:
_hw_pin.parent.read = _pin_read; //把设备的read操作绑定在pin.c的_pin_read函数 _hw_pin.parent.write = _pin_write; _hw_pin.parent.control = _pin_control; 这说明我们不仅可以使用 rt_pin_read 获取 pin 设备的值,还可以使用 rt_device_read 获取 pin 设备的值!!!
❤️ 在 rt-thread 的 pin 设备模型中, rt_pin_read 函数和 rt_device_read 函数效果一样。
1.4 pin 设备驱动层 pin 设备驱动层,直接与硬件打交道的层面,对于我们使用的 stm32 来说,里面的很多操作我们应该都不会陌生,我们也简单了解下里面的函数,主要的目的在于实现 pin 设备控制块中 rt_pin_ops 成员中的几个函数:
实现的函数static const struct pin_index *get_pin(uint8_t pin)static void stm32_pin_write(rt_device_t dev, rt_base_t pin, rt_base_t value)static int stm32_pin_read(rt_device_t dev, rt_base_t pin)static void stm32_pin_mode(rt_device_t dev, rt_base_t pin, rt_base_t mode)rt_inline rt_int32_t bit2bitno(rt_uint32_t bit)rt_inline const struct pin_irq_map *get_pin_irq_map(uint32_t pinbit)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)static rt_err_t stm32_pin_dettach_irq(struct rt_device *device, rt_int32_t pin)static rt_err_t stm32_pin_irq_enable(struct rt_device *device, rt_base_t pin, rt_uint32_t enabled)/*一个重要的结构体*/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,};rt_inline void pin_irq_hdr(int irqno)void hal_gpio_exti_callback(uint16_t gpio_pin)void exti0_irqhandler(void)...//一系列的外部中断函数...int rt_hw_pin_init(void) 我们简单来看一个函数,根本不需要过多的解释:
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);} 初始化函数 初始化函数虽然重要,但是简单,看一下就能明白,首先就是熟悉的 gpio 时钟初始化,
然后就是调用设备注册函数,设备名称 pin ,也是在这里定义的,如果改成其他的,在 shell 工具中使用 list_device 就会显示其他的名称了。
第二个参数,就是将设备驱动层中实现的对硬件的操作函数关联到 pin 设备驱动框架层以供应用程序使用用。
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 引脚的定义方式(stm32为例),我们有必要说明一下。
与 uart 不同的是,gpio 配置简单能够更直接关联硬件,所以 hal 库并没有为 gpio 提供句柄结构体描述,在 hal 库中描述 gpio 使用了两个参数:gpio_typedef* gpiox和gpio_pin,比如:
gpio_pinstate hal_gpio_readpin(gpio_typedef* gpiox, uint16_t gpio_pin);void hal_gpio_writepin(gpio_typedef* gpiox, uint16_t gpio_pin, gpio_pinstate pinstate);void hal_gpio_togglepin(gpio_typedef* gpiox, uint16_t gpio_pin); 而在 rt-thread 中,其定义了一个结构体 pin_index,通过一个变量即可描述一个 gpio,如下:
/* stm32 gpio driver */struct pin_index{ int index; gpio_typedef *gpio; uint32_t pin;}; 针对这个结构体,驱动程序中给了一些补充的操作:
/*相当于结构体pin_index以宏定义的形式被初始化用c语言字符串连接定义引脚信息index 引脚的索引号,用户可自行定义的驱动程序定义的引脚编号gpio 相当于hal库中的gpio_typedef gpio_index 相当于hal库中的gpio_pin例如宏__stm32_pin(0, a, 0) 就表示结构体内容为 {0, gpioa, gpio_pin_0}*/#define __stm32_pin(index, gpio, gpio_index) \ { \ index, gpio##gpio, gpio_pin_##gpio_index \ }//保留未使用的宏定义,有些io口未使用,使用这个宏定义#define __stm32_pin_reserve \ { \ -1, 0, 0 \ }static const struct pin_index pins[] = {#if defined(gpioa) __stm32_pin(0 , a, 0 ), __stm32_pin(1 , a, 1 ), __stm32_pin(2 , a, 2 ), __stm32_pin(3 , a, 3 ), __stm32_pin(4 , a, 4 ), __stm32_pin(5 , a, 5 ), __stm32_pin(6 , a, 6 ), __stm32_pin(7 , a, 7 ), __stm32_pin(8 , a, 8 ), __stm32_pin(9 , a, 9 ), __stm32_pin(10, a, 10), __stm32_pin(11, a, 11), __stm32_pin(12, a, 12), __stm32_pin(13, a, 13), __stm32_pin(14, a, 14), __stm32_pin(15, a, 15),#if defined(gpiob) __stm32_pin(16, b, 0), __stm32_pin(17, b, 1), //后面省略很多......
首先宏定义#define __stm32_pin(index, gpio, gpio_index) :
其中##为c语言连接符,其功能是在带参数的宏定义中将两个子串(token)联接起来,从而形成一个新的子串,例如宏__stm32_pin(0, a, 0) 就表示结构体内容为 {0, gpioa, gpio_pin_0},就等于定义了一个pin_index结构体。
然后宏定义__stm32_pin_reserve :
预留的io楼,有些io口未使用,使用这个宏定义
接下来的结构体数组pins :
pins 为pin_index结构体类型的数组,rt-thread 使用 pins 数组对 所有的 gpio 引脚进行初始化定义。
这样就相当于芯片上所支持的 io 口都进行了初始化定义,每一个 gpio 都有了一个对应的索引号index。
在 rt-thread 提供的 pin 设备操作函数中void rt_pin_mode(rt_base_t pin, rt_base_t mode);, 他的第一个参数也不是类似 pin设备控制块之类的数据结构,而是一个引脚索引号,就是对应的上面这个index。
引脚中断的分析和 引脚定义类似,可自行查看代码,这里就不过多说明。
二、pin 设备操作函数 文章开头我们虽然已经认识过 pin 设备的操作函数,但是我们没有对函数参数可取值做说明,学习 api 的使用还是老样子,直接放函数原型然后看注释。
2.1 获取 pin 索引号方法 在我们使用某个 gpio 的时候,第一步要做的就是获取 gpio 的索引号,即上文说到的index。因为对 pin 设备的访问操作都是通过这个索引号进行的。
在 rt-thread 中,提供了 3种方式获取 pin 设备索引号:
方法一: 使用函数rt_pin_get():
在 pin.c 文件中提供了一个函数:
rt_base_t rt_pin_get(const char *name) 里面的参数为一个名字,那么这个名字是什么呢?在函数申明有注释:
对于stm32而言,使用示例如下:
//获取索引号pin_number = rt_pin_get(pa.9); // pin_number 就是索引号//设置gpio模式rt_pin_mode(pin_number , pin_mode_input_pullup);
方法二: 使用宏定义get_pin:
在drv_common.h文件中有宏定义,可以直接获取 gpio 的索引号:
#define __stm32_port(port) gpio##port##_base#define get_pin(portx,pin) (rt_base_t)((16 * ( ((rt_base_t)__stm32_port(portx) - (rt_base_t)gpioa_base)/(0x0400ul) )) + pin)
对于stm32而言,使用示例如下:
//获取索引号#define led0_pin get_pin(b, 9)//led0 点亮或者熄灭#define led0(n) (n ? rt_pin_write(led0_pin, pin_high) : rt_pin_write(led0_pin, pin_low))
方法三: 查看驱动文件drv_gpio.c:
上面讲解 pin 设备驱动层的时候说到过,所有的 gpio 对应的索引号都会在驱动文件中定义,直接查看文件使用索引号就可以:
对于stm32而言,使用示例如下:
//对应驱动文件,下面的代码含义就是 设置 pa0 的模式为 pin_mode_input_pulluprt_pin_mode(0, pin_mode_input_pullup); 说明,查看驱动文件的方式并不直观。
2.2 操作函数 操作函数说明老样子
2.2.1 设置 gpio 模式/*参数 描述pin 引脚编号:索引号mode 引脚工作模式工作模式可选:#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 开漏输出*/void rt_pin_mode(rt_base_t pin, rt_base_t mode); 2.2.2 设置/ 读取 gpio 电平 设置引脚电平:
/*参数 描述pin 引脚编号value 电平逻辑值,value 取值:pin_low 低电平,pin_high 高电平*/void rt_pin_write(rt_base_t pin, rt_base_t value); 读取引脚电平:
/*参数 描述pin 引脚编号返回 pin_low 低电平pin_high 高电平*/int rt_pin_read(rt_base_t pin); 2.2.3 绑定/脱离中断回调函数 绑定中断回调函数:
/*参数 描述pin 引脚编号mode 中断触发模式hdr 中断回调函数,用户需要自行定义这个函数args 中断回调函数的参数,不需要时设置为 rt_null返回 ——rt_eok 绑定成功错误码 绑定失败其中 mode 可选参数:#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_attach_irq(rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args);
脱离中断回调函数:
/*参数 描述pin 引脚编号返回 ——rt_eok 脱离成功错误码 脱离失败*/rt_err_t rt_pin_detach_irq(rt_int32_t pin); 说明:引脚脱离了中断回调函数以后,中断并没有关闭,还可以调用绑定中断回调函数再次绑定其他回调函数。
2.2.4 使能中断 绑定好引脚中断回调函数后需要使用下面的函数使能引脚中断:
/*参数 描述pin 引脚编号enabled 状态返回 ——rt_eok 使能成功错误码 使能失败enabled 可取 2 种值之一:pin_irq_enable (开启)pin_irq_disable (关闭)*/rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled); 三、pin 设备示例 只要明白了pin 设备模型原理,使用起来还是很简单的,我们先看一下原理图:
程序如下,测试ok,太简单所以没有什么好说的:
...//添加这两个头文件#include #include board.h...static struct rt_thread led1_thread; //led1线程static char led1_thread_stack[256];static rt_thread_t key1_thread = rt_null; //#define led1_pin get_pin(d, 9)#define led2_pin get_pin(d, 8)#define key1_pin get_pin(d, 11)#define key2_pin get_pin(d, 10)#define key1_read rt_pin_read(key1_pin)#define led1_on rt_pin_write(led1_pin, pin_low);#define led1_off rt_pin_write(led1_pin, pin_high);#define led2_on rt_pin_write(led2_pin, pin_low);#define led2_off rt_pin_write(led2_pin, pin_high);//#define led0(n) (n ? rt_pin_write(led0_pin, pin_high) : rt_pin_write(led0_pin, pin_low))static void led1_thread_entry(void *par){ while(1){ led1_on; rt_thread_mdelay(1000); led1_off; rt_thread_mdelay(1000); }}static void key1_thread_entry(void *par){ while(1){ if(key1_read == 0){ rt_thread_mdelay(10); //去抖动 if(key1_read == 0){ rt_kprintf(key1 kicked..\r\n); } while(key1_read == 0){rt_thread_mdelay(10);//去抖动 } } rt_thread_mdelay(1); }}int main(void){ mx_usart1_uart_init(); // mx_gpio_init(); //使用设备模型不需要初始化这个 /*配置led管脚为输出*/ rt_pin_mode(led1_pin, pin_mode_output); rt_pin_mode(led2_pin, pin_mode_output); /*配置按键为输入*/ rt_pin_mode(key1_pin, pin_mode_input); rt_pin_mode(key2_pin, pin_mode_input); /*led默认状态*/ rt_pin_write(led1_pin, 1); rt_pin_write(led2_pin, 0); rt_err_t rst2; rst2 = rt_thread_init(&led1_thread, led1_blink , led1_thread_entry, rt_null, &led1_thread_stack[0], sizeof(led1_thread_stack), rt_thread_priority_max -1, 50); if(rst2 == rt_eok){ rt_thread_startup(&led1_thread); } key1_thread = rt_thread_create(key1_control, key1_thread_entry, rt_null, 512, rt_thread_priority_max -2, 50); /* 如果获得线程控制块,启动这个线程 */ if (key1_thread != rt_null) rt_thread_startup(key1_thread); ...//后面省略 结语 本文我们详细的分析了 rt-thread i/o 设备模型之pin设备,最终看来,使用 pin 设备模型操作还是特别的简单的。
其实关键的部分还是在于理解 pin 设备模型的原理,理解了以后使用起来也更加的得心应手。
gpio设备虽然简单,但是文章写下来也1w多字了,即便以前对 pin 设备有点模糊,只要看了本文,相信大家肯定有拨云见日的感觉!
希望大家多多支持!本文就到这里,谢谢!
一文带你了解WMS软件在仓库中的应用价值
使用运算放大器OPA735的跟随电路设计
商业和数据之间的关系怎样被区块链改变
受益于5G 深南电路等厂商孕育新机遇
腾讯副总裁:微信不会读取、存储微信聊天记录
RT-Thread记录(十三、I/O 设备模型之PIN设备)
dsp2812开发板原理及原理图分析
单极性码,单极性码是什么意思
联发科发布全新8K旗舰智能电视芯片
中国智造登台!斯坦德机器人亮相日本国际物流综合展
永不言弃 做真实的自己 OPPO R15 Slogan态度海报惊艳发布
黑芝麻智能正式发布瀚海-ADSP自动驾驶中间件平台
安全代币的优点有哪些
通过智能魔镜显示屏来将黑科技融入我们的家居生活
物联网等新兴技术有利于改革消防安全
最新YS4004手势模组DEMO板登场
无线气体传感器概述、选型及应用
Q2小米手机出货量同比下滑26%
安路科技推出集成不同接口的FPGA 器件
电气设备固定件的安装技巧