PIN设备又称GPIO(General Purpose Input Output,通用输入/输出)设备,又简称为I/O口,指MCU上的I/O引脚。RT-Thread将通用I/O抽象为PIN设备,以实现通用I/O口的功能。MCU的通用I/O一般用于读取引脚的输入电平或者控制引脚的输出电平,进而控制MCU的外围电路。常用硬件连接图如图3-1所示,图中MCU外接的是LED和KEY。
图3-1 LED/KEY的硬件连接图
在图3-1中,LED_R/G/B、KEY0/1/2均直连MCU GPIO口。图3-1a所示为MCU输出高低电平,以控制LED亮灭;图3-1b所示为MCU检测高低电平,以判断按键是否按下。
RT-Thread的PIN设备抽象了I/O口的基本功能,而I/O口的基本功能如下。
1)通过读取引脚的电平检测引脚外部输入情况,输入情况分为输入浮空、输入上拉、输入下拉、模拟输入。
2)设置引脚输出电平,输出情况分为开漏输出、推挽式输出。
3)检测外部中断:当I/O外部输入的电平变化时,可以触发给I/O引脚设置的中断。
以上I/O基本操作均被抽象为P I N设备。除了通用I/O之外,片上外设复用功能被RT-Thread抽象为其他外设,如第2章的UART设备,以及后续章节讲解的其他外设。
PIN的层级结构如图3-2所示。
1)应用层一般是由开发者编写的业务代码,通过调用PIN设备框架提供的统一接口完成具体的业务代码编写,如LED、蜂鸣器的驱动代码。
2)PIN设备驱动框架层和平台无关,是通用的软件层,向应用层提供统一的接口供应用层调用。PIN设备驱动框架源码是pin.c,位于RT-Thread源码的components\drivers\misc文件夹中,PIN设备驱动框架的功能有以下两点。
①PIN设备驱动框架向应用层提供PIN设备管理接口,即rt_pin_read、rt_pin_write、rt_pin_mode等(接口名均使用rt_前缀),应用程序使用这些接口来操作硬件引脚。
②PIN设备驱动框架向PIN设备驱动提供PIN设备操作方法接口struct rt_pin_ops(具体如pin_get、pin_mode、pin_write、pin_read、pin_attach_irq、pin_detach_irq、pin_irq_enable),PIN设备驱动需要实现这些接口。除此之外,该层还向PIN设备驱动提供PIN设备注册接口rt_device_pin_register, PIN设备驱动需要调用该接口进行设备注册。
3)PIN设备驱动层的实现与具体硬件平台相关,通过调用厂商提供的库函数或寄存器来操作具体的硬件GPIO口。PIN设备驱动源码文件drv_gpio.c放置在具体的BSP目录下,与BSP相关。PIN设备驱动需要实现PIN设备的操作方法接口struct rt_pin_ops,这些操作方法提供了访问和控制PIN硬件的能力。该驱动也负责调用rt_device_pin_register函数来注册PIN设备到操作系统中。
图3-2 PIN层级结构图
4)最下面一层是与硬件GPIO口相连接的常用模块,有LED、按键、蜂鸣器等。
我们再来看一下PIN设备驱动的具体开发步骤。PIN设备驱动开发主要任务是实现PIN设备操作方法rt_pin_ops,然后注册PIN设备。驱动文件一般命名为drv_gpio.c。本章将会以STM32的PIN设备驱动为例讲解PIN驱动的具体实现。
1.操作方法原型
PIN设备的操作方法rt_pin_ops定义在PIN设备框架中,包含了和硬件相关的操作方法,其结构体原型如下:
(1)成员说明
1)pin_get:获取某个PIN的引脚编号。
2)pin_mode:将某个引脚初始化为对应的工作模式。
3)pin_write:设置某个引脚的输出电平。
4)pin_read:读取某个引脚的电平。
5)pin_attach_irq:中断操作,为某个引脚绑定一个中断回调函数,使能中断后,当中断到来时调用该函数。
6)pin_irq_enable:中断操作,开启或关闭中断。
7)pin_detach_irq:中断操作,脱离某个引脚的中断回调函数。
(2)引脚编号的作用
不同厂商对引脚的定义不相同,确定一个引脚所需的参数也不同,举例如下。
1)ST:引脚由PORT名和PIN名组成,PORT名为英文字母A~Z,PIN名为数字0~15,如PA0、PB5。如果使用STM32的HAL库来控制某一个引脚,需要提供如下类型的参数组合:(GPIO_TypeDef*GPIOx, uint16_t GPIO_Pin)。
2)NXP: LPC系列引脚名也是由PORT名和PIN名组成,但是PORT名由数字组成,如PIO0_0、PIO2_15。如果使用其提供的库函数来控制某一个引脚,需要提供的参数组合为(GPIO_Type*base, uint32_t port, uint32_t pin)。
若是在应用层直接使用各厂商提供的写法,则应用层代码不可能具有良好的通用性。为了使基于PIN设备驱动框架开发的应用代码兼容各个硬件平台,于是给每个引脚进行编号(0、1、2……),每个编号代表唯一一个引脚,这样基于PIN设备驱动框架开发的代码就有了良好的通用性。引脚编号可以直接使用MCU的引脚编号,也可以自定义一个引脚编号表,自定义编号表的编号顺序不一定和MCU引脚编号相同。
2.为设备定义操作方法
驱动开发者需要在驱动文件中实现这些操作方法。以STM32为例,首先使用struct rt_pin_ops实例化一个如下所示的结构,然后实现对应的函数。
3.pin_get:获取引脚编号
操作方法pin_get是用于获取某个PIN引脚编号的接口,其原型如下所示:
pin_get方法的参数及返回值如表3-1所示。
表3-1 pin_get方法的参数及返回值
注意,用户对PIN设备的操作主要是通过引脚编号,不同厂商为芯片引脚定义的名称不同,如ST的是PA0,NXP的是PIO0_0,在RT-Thread中,使用“.”作为PORT名与PIN名的分割,将它们统一为PA.0、P0.0。
驱动开发者在实现该操作方法时,需要根据传入的name参数,返回匹配的引脚编号数值。
我们来看一个获取引脚编号的示例,以下是STM32 PIN设备实现的pin_get接口的部分代码,我们可通过MCU引脚排列规律得出引脚编号。
4.pin_mode:设置引脚工作模式
操作方法pin_mode是用于设置引脚工作模式的接口,如上/下拉模式或输入/输出模式,其原型如下所示:
pin_mode方法的参数如表3-2所示。
表3-2 pin_mode方法的参数
其中,参数device表示PIN的设备句柄,因为PIN设备在实现时每一个引脚都会由引脚编号进行代替,所以device参数目前不再使用。
在表3-2中,mode的取值如下所示。驱动需要根据指定的模式对MCU的引脚进行相应的配置。驱动框架提供的模式有以下几种:
需要注意的是,mode参数会从应用层传入,驱动层需要根据以上mode的值完成对应的操作。
如下是设置引脚工作模式的示例,可根据上面的5种模式对引脚进行设置,即STM32 PIN驱动的pin_mode接口实现。
其他芯片的驱动可以参考如上代码完成pin_mode这个操作方法。
5.pin_write:设置引脚电平
操作方法pin_write是用于设置引脚的电平状态的接口,通过传入的引脚编号和引脚电平状态,对引脚进行设置,其原型如下所示:
pin_write方法的参数如表3-3所示。
表3-3 pin_write方法的参数
我们来看一个在STM32上实现pin_write接口的示例代码,首先根据引脚编号获取引脚编号映射表的索引,索引包含引脚的端口信息和引脚信息,之后调用库函数修改引脚电平状态。
6.pin_read:读取引脚电平
操作方法pin_read是用于读取引脚电平状态的接口,通过传入的引脚编号获取该引脚的状态,并将状态返回,其原型如下所示:
pin_read方法的参数及返回值如表3-4所示。
表3-4 pin_read方法的参数及返回值
我们来看一个在STM32上实现pin_read方法的示例,首先根据引脚编号获取引脚编号映射表的索引,索引包含引脚的端口信息和引脚信息,之后调用库函数读取引脚电平状态,最后返回该状态。
7.pin_attach_irq:引脚绑定中断
操作方法pin_attach_irq用于给某一个引脚绑定中断回调函数,该方法在实现时需要根据中断触发方式实现中断功能,触发方式有上升沿触发、下降沿触发等,还需绑定引脚中断回调函数,用于触发中断之后执行的动作,其原型如下所示:
pin_attach_irq方法的参数及返回值如表3-5所示。
表3-5 pin_attach_irq方法的参数及返回值
在表3-5中,引脚中断触发模式有5种宏定义值,若遇到芯片不支持的模式,在驱动中直接返回错误即可。
回调函数指针hdr需要被保存起来,例如保存在一张中断回调函数表中(方法不限于此),与引脚编号和中断触发模式等属性一一对应,待中断触发时,通过查表调用绑定的回调函数,完成中断的执行,以下是中断回调函数表。
除此之外,驱动开发人员需完成相应的中断处理函数。以CORTEX-M为例,若PA0引脚开启外部中断,并进行外部触发,则需实现如下类似代码:
下面以STM32 PIN为例讲解引脚绑定中断。
1)实现中断回调函数表(方法不限于此),如STM32最多有16条外部中断线,即最多可同时打开16个外部中断,所以中断回调函数表初始化如下所示:
2)STM32 PIN驱动pin_attach_irq接口的部分代码如下所示,首先根据pin引脚编号获取到中断回调函数表的索引,然后为回调表对应索引的元素赋值。
当使能中断,触发外部中断时,进入中断并调用回调函数。需要注意的是,进出中断时必须分别调用rt_interrupt_enter()和rt_interrupt_leave()。
8.pin_irq_enable:引脚中断使能控制
操作方法pin_irq_enable用于设置引脚的中断开关状态,其原型如下所示:
pin_irq_enable方法的参数及返回值如表3-6所示。
表3-6 pin_irq_enable方法的参数及返回值
来看一个引脚中断使能控制的示例,PIN驱动根据参数enabled开启或者关闭引脚中断。STM32 PIN驱动pin_irq_enable方法的部分代码如下所示。
9.pin_detach_irq:引脚脱离中断
操作方法pin_detach_irq是用于脱离引脚绑定的中断回调函数的接口,该方法根据传入的引脚编号,脱离该引脚已经绑定的中断回调函数,其原型如下所示:
pin_detach_irq方法的参数及返回值如表3-7所示。
表3-7 pin_detach_irq方法的参数及返回值
引脚脱离中断示例如下所示,PIN驱动根据参数引脚编号,重置对应的引脚中断回调函数表。STM32 PIN驱动的pin_detach_irq方法的部分代码如下所示。
PIN设备的操作方法都实现后,需要注册设备到操作系统,注册时提供PIN设备的名称和操作方法作为入参,注册PIN设备的接口原型如下所示:
rt_device_pin_register接口的参数与返回值如表3-8所示。
表3-8 rt_device_pin_register接口的参数与返回值
STM32 PIN驱动注册PIN设备的代码片段如下所示。
其中,stm32_pin_ops是在3.2节实现设备的操作方法时定义的,保存了PIN所有操作方法的函数指针。
下面是具体的驱动配置细节。
1.Kconfig配置
下面参考bsp/stm32/stm32f407-atk-explorer/board/Kconfig文件对PIN驱动进行相关配置,如下所示:
我们来看看一些关键字段的意义。
❏BSP_USING_GPIO: PIN驱动代码对应的宏定义,这个宏控制决定PIN驱动相关代码是否会添加到工程中。
❏RT_USING_PIN: PIN驱动框架代码对应的宏定义,这个宏控制PIN驱动框架的相关代码是否会添加到工程中。
❏defaulty: BSP_USING_GPIO宏默认打开。
2.SConscript配置
HAL_Drivers/SConscript文件给出了PIN驱动添加情况的判断选项,代码如下所示。这是一段Python代码,表示如果定义了宏BSP_USING_GPIO, drv_gpio.c会被添加到工程的源文件中。
驱动开发者完成驱动的编写后,还需进行驱动的验证,保证编写的驱动可以正常使用,PIN驱动验证流程如下所示。
编写的驱动会将PIN设备注册到系统中,此时PIN设备将在I/O设备管理器中存在。验证设备功能时,我们需要运行代码,将代码编译下载到开发板中,然后在控制台中使用list_device命令查看已注册的设备,此时已经包含PIN设备,如下所示。
之后则可以使用PIN设备驱动框架层提供的统一API对PIN进行操作了。
若开发板上有按键或者LED灯,可以编写简单的测试代码来对驱动进行测试验证。如ART-Pi开发板上有两个LED灯,其中蓝灯D2连接在PE4引脚上,可以使用PIN框架提供的API来控制PE4引脚的电平状态,从而控制LED灯的亮灭了,测试代码如下:
代码运行效果:开发板上的蓝灯D2会周期性闪烁。
本章讲解了如何开发PIN设备驱动、如何将PIN设备对接到设备驱动框架、如何验证PIN设备驱动是否可用。