一、gpio用途
General Purpose Input Output (通用输入/输出)简称为GPIO,或总线扩展器,人们利用工业标准I2C、SMBus或SPI接口简化了I/O口的扩展。当微控制器或芯片组没有足够的I/O端口,或当系统需要采用远端串行通信或控制时,GPIO产品能够提供额外的控制和监视功能。
每个GPIO端口可通过软件分别配置成输入或输出。Maxim的GPIO产品线包括8端口至28端口的GPIO,提供推挽式输出或漏极开路输出。提供微型3mm x 3mm QFN封装。
不同系统间的GPIO的确切作用不同。通用常有下面几种:
1.输出值可写(高=1,低=0)。一些芯片也可以选择驱动这些值的方式,以便支持“线-或”或类似方案(开漏信号线)。
2.输入值可读(1,0)。一些芯片支持输出管脚回读,这在线或的情况下非常有用(以支持双向信号线)。GPIO控制器可能具有一个输入防故障/防反跳逻辑,有时还会有软件控制。
3.输入经常被用作中断信号,通常是边沿触发,但也有可能是电平触发。这些中断可以配置为系统唤醒事件,从而将系统从低功耗模式唤醒。
4.一个GPIO经常被配置为输入/输出双向,根据不同的产品单板需求,但也存在单向的情况。
5.大多是GPIO可以在获取到spinlock自旋锁时访问,但那些通过串行总线访问的通常不能如此操作(休眠的原因)。一些系统中会同时存在这两种形式的GPIO。
6.在一个给定单板上,每个GPIO用于一个特定的目的,如监控MMC/SD卡的插入/移除,检查卡写保护状态,驱动LED,配置发送器,串行总线位拆,触发一个硬件看门狗,触发一个开关之类的。
二、gpio优点
低功耗:GPIO具有更低的功率损耗(大约1μA,μC的工作电流则为100μA)。
集成IIC从机接口:GPIO内置IIC从机接口,即使在待机模式下也能够全速工作。
小封装:GPIO器件提供最小的封装尺寸 ― 3mm x 3mm QFN!
低成本:您不用为没有使用的功能买单。
快速上市:不需要编写额外的代码、文档,不需要任何维护工作。
灵活的灯光控制:内置多路高分辨率的PWM输出。
可预先确定响应时间:缩短或确定外部事件与中断之间的响应时间。
更好的灯光效果:匹配的电流输出确保均匀的显示亮度。
布线简单:仅需使用2条就可以组成IIC总线或3条组成SPI总线。
与ARM 的几组GPIO引脚,功能相似,GPxCON 控制引脚功能,GPxDAT用于读写引脚数据。另外,GPxUP用于确定是否使用上拉电阻。 x为A,B,,H/J,
GPAUP 没有上拉电阻。
三、GPIO系统结构
如前面提醒的一样,一个可选的实现结构使得平台支持不同种类的GPIO控制器使用同一个编程接口变得简单。这个结构称为gpiolib。
作为一个调试目的,如果debugfs有效,一个/sys/kernel/debug/gpio文件在那里将被找到。它列出了所有的通过这个结构注册的GPIO控制器,和GPIO当前的使用状态。
Controller Drivers: gpio_chip
控制器驱动:gpio_chip
在这个架构中,每个GPIO控制器被封装为一个“gpio_chip”结构体,此结构体中包含了每个控制器的通用信息:
--确定GPIO方向的方法
--存取GPIO值的方法
--声明方法是否休眠的flag
--可选的debugfs dump方法(展现附加的状态如上拉配置等)
--用于诊断目的的标签
每个实例也有自己的私有数据,可能来自device.platform_data:它的第一个GPIO和它暴露几个GPIO.
实现一个gpio_chip的代码应该支持多个控制器的实例,可能使用驱动模型。代码会配置每个gpio_chip并且执行gpiochip_add()。移除一个GPIO控制器是少见的,使用gpio_remove()移除一个不再有效的GPIO控制器。
大多数时候,一个gpio_chip是一个实例独有的结构,它的一些状态值不暴露给GPIO接口,如编址、电源管理等等。编码解码器之类的芯片会有复杂的非GPIO状态。
所有的debugfs dump 方式通常应该忽略那些未作为GPIO请求的信号。他们可以使用gpiochip_is_requested(),此函数返回与GPIO相关的label或是NULL。
平台支持
为了支持这个结构,一个平台的Kconfig需要选择ARCH_REQUIRE_GPIOLIB或是ARCH_WANT_OPTIONAL_GPIOLIB之一,且安排的它的《asm/gpio.h》包含《asm-generic/gpio.h》并且定义3个函数gpio_get_value(), gpio_set_value(), 和 gpio_cansleep()。
他也可以提供一个自定义的值:ARCH_NR_GPIOS,以便能更好的反映平台实际使用的GPIO数目,并不浪费静态区域空间。(它应该计数 内建/SOC GPIO和GPIO扩展器扩展的数目)
ARCH_REQUIRE_GPIOLIB意味着此平台上gpiolib代码将永久编译进内核
ARCH_WANT_OPTIONAL_GPIOLIB意味着gpiolib代码默认是关闭的,用于可以使能它并且将它可选的编译进内核。
如果这些选项都未被选上,平台不能通过GPIO-lib支持GPIO,这些代码也不能被用户使能。
那些函数琐细的实现可以直接使用架构代码,它们经常通过gpio_chip分配:
#define gpio_get_value__gpio_get_value
#define gpio_set_value__gpio_set_value
#define gpio_cansleep__gpio_cansleep
爱好者实现可以代替定义他们使用内联函数,使用逻辑优化存取特定的基于SOC的GPIO。例如,如果 引用的GPIO是常数“12”,getting或setting它的值可能只需要2个或3个指令,且从不休眠。如果这样一个优化是不可能的话,这些调用实现必须委托给架构代码,它会耗费至少几十个指令。为了位拆型I/O,这些指令的节约是有相当大的意义的。
对于SOC来说,平台特定的代码为每个bank的片上GPIO定义和注册了gpio_chip实例。那些GPIO应该被编号和打上标签以匹配芯片厂商文档,且直接匹配单板设计图。他们可以从零开始一直到平台特定的限制。这些GPIO通常集成到单板初始化过程中以使得它们总是有效的,从arch_initcall()到更早,它们总是可以为中断服务。
板级支持
对于外部GPIO控制器(如I2C或SPI扩展)、ASIC、多功能器件、FPGA或是CPLD,通常单板私有代码例程注册控制器器件且确定它们的驱动使用什么GPIO号来调用gpiochip_add。它们的号码经常在平台特定GPIO之后开始。
例如,单板setup代码可以创建结构标示芯片想要暴露的GPIO的范围,且使用platform_data传递它们到每个GPIO扩展器芯片。这样芯片驱动的probe()历程可以传递这些数据到gpiochip_add()。
初始化顺序是很重要的。例如当一个依赖于基于I2C的GPIO的设备,它的probe()例程应该仅能在GPIO有效后调用。这意味着设备不能在GPIO可以工作之前注册。一个解决这样依赖的方法是在板级特定代码中,对于这种gpio_chip控制器来提供setup()和teardown()回调,这些板级特定的回调将注册设备一旦所有的需要资源有效时,并且在GPIO控制器无效时将它们移除。
用户空间的Sysfs接口(可选)
使用gpiolib实现结构的平台可以选择为GPIO配置一个sysfs用户接口。这与debugfs接口不同,因为它提供了覆盖GPIO方向和值的控制而不只是显示一个gpio状态信息摘要。另外,它可以在产品系统中提供而不需要调试支持。
为系统给出对应的硬件文档,用户空间可以知道例如GPIO#23控制着保护线,用于保护flash中的boot区域。系统升级程序可能需要临时移除这个保护,首先引入一个GPIO,然后改变它的输出状态,接下来在重新使能写保护之前升级代码。通常用法中,GPIO#23将不会被触碰,并且内核也不需知道它的信息。
同样依靠一个合适的硬件文档,在一些系统用户空间,GPIO可以被用于决定那些内核并不关心的系统配置数据。对于一些任务,简单用户空间GPIO驱动是系统真正需要的
注意,针对通用“LED和按钮”的标准内核驱动存在对应的GPIO任务“leds-gpio”和“gpio-keys”。使用它们代替直接与GPIO通话,它们集成在内核架构比你的用户态代码可能更好。
Paths in Sysfs
Sysfs路径
There are three kinds of entry in /sys/class/gpio:
/sys/class/gpio有3个入口条目:
-控制接口用于用户空间获取GPIO控制
-GPIO自己
-GPIO控制器(“gpio_chip”实例)
这是对于标准文件的补充,包括“device”符号
控制接口是只写的:
/sys/class/gpio/
“export” ————通过写GPIO的号码到此文件,用户空间可以要求内核导出一个GPIO的控制到用户空间
例如:“echo 19 》 export”将创建一个GPIO #19的“gpio19”节点(假设内核代码未申请此GPIO号)。
“unexport”————与“export”效果相反
例如:“echo 19 》 unexport”将移除一个由“export”文件导出的“gpio19”节点。
GPIO信号拥有如/sys/class/gpio/gpio42/(对应于GPIO#42)的路径,并且具有下列读写属性:
/sys/class/gpio/gpioN/
“direction”————读为“in”或是“out”。这个值通常可写。写“out”默认初始化此值为低。为了确定无障碍操作,值“low”和“high”可以被写入以配置GPIO的输出初始化值。
注意这个属性“将不存在”如果内核不支持改变一个GPIO的方向,或者它不能被内核代码导出(不能显式的允许用户空间来重新配置GPIO的选项。)
“value”—————读作“0”(低)或“1”(高)。如果GPIO被配置为一个输出,这个值可写;任何非零值均被视为高。
如果管脚可以被配置为中断产生中断管脚,且如果它已经被配置为产生中断(参考“edge”描述),你可以poll(2)此文件并且当中断触发时poll(2)将返回。如果你使用了poll(2),设置POLLPRI和POLLERR事件。如果你使用select(2),在exceptfds中设置文件描述符。在poll(2)返回之后,有两个选择一是lseek(2)到sysfs文件的开始且读新的值,另一个是关闭文件且重打开它来读取新的值。(为何这样设置?)
“edge”————读作“none”、“rising”、“falling”或是“both”。写这些字符串以选择边沿信号,他将使得“value”文件上的poll(2)操作返回。
这个文件只在管脚可以配置为中断产生输入管脚时存在。
“active_low”————读为0(false)或1(true)。写任何非零值都会反转读或写的值。目前和后来的poll(2)支持经由edge属性配置为“rising”或“falling”上升沿或下降沿将遵循这个设置。
GPIO控制器具有如/sys/class/gpio/gpiochip42/(针对控制器,实现GPIO开始于#42)的路径,且具有下列制度属性:
/sys/class/gpio/gpiochipN/
“base”————与N相等,是第一个被此芯片管理的GPIO
“label”————提供用于诊断(并不总是独一无二的)
“ngpio”————管理的GPIO数(N到N+ngpio-1)
大多数情况下,单板文档应该提供GPIO的使用目的。虽然如此这些号码并非总是固定的,一个子板上的GPIO可能与基础板使用的不同。此种情况下,你可能需要使用gpiochip节点(可能与设计结合)来为每个信号决定正确的GPIO号码。
从内核代码中导出
内核代码可以显式管理那些使用gpio_request()申请的GPIO的导出
/* export the GPIO to userspace */
int gpio_export(unsigned gpio, bool direction_may_change);
/* reverse gpio_export() */
void gpio_unexport();
/* create a sysfs link to an exported GPIO node */
int gpio_export_link(struct device *dev, const char *name,
unsigned gpio)
/* change the polarity of a GPIO node in sysfs */
int gpio_sysfs_set_active_low(unsigned gpio, int value);
一个内核驱动申请一个GPIO后,它可以使用gpio_export()使得sysfs接口有效。驱动可以控制信号方向是否可以改变。这使得驱动可以防止用户空间代码不小心冲击重要的系统状态。
明确的exporting有助于调试(使得一些实验更简单),或是提供一个总是可以使用的接口,适合于bsp文档。
GPIO被导出后,gpio_export_link()允许在sysfs的任何地方创建GPIO sysfs节点的符号链接。驱动可以用此在它们自己设备sysfs目录下提供指定名字的接口(链接到GPIO节点)
驱动可以使用gpio_sysfs_set_active_low()隐藏GPIO在用户空间和单板之间的线极性不同。这仅影响sysfs接口。极性变换可以在gpio_export()之前和之后完成,并且前面使能的poll(2) (支持上升沿或下降沿事件)将被重新配置为遵循此设置。
四、GPIO使用方法
要使用GPIO,系统首先要分配一个GPIO,使用gpio_request() 为系统分配一个GPIO。
接下来要做的一件事是标示GPIO的方向,通常在使用GPIO建立一个platform_device时(位于单板的setup代码中):
/* set as input or output, returning 0 or negative errno */
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
返回0标示成功,或是一个负的errno错误码。它应该被检查,因为get/set调用没有错误返回,且可能会有错误配置。你通常应该在线程上下文中使用这些调用。虽然如此,对于spinlock-safe的GPIO,在tasking使能之前使用也是可以的,作为一个早期的单板建立。
对于输出GPIO,value参数提供了初始输出值。这有助于避免系统启动过程中的信号干扰。
为了与GPIO早期的接口兼容,设置一个GPIO的方向,隐性要求申请GPIO。这个兼容性从可选的gpiolib架构中移除了。
如果GPIO号码无效或是指定的GPIO不能使用对应模式操作的话,设置方向会失败。依靠boot固件设置好GPIO的方向通常不是一个好主意,因为boot的功能可能没有通过验证(除了boot linux)。(类似的,单板setup代码可能需要将管脚复用为一个GPIO,和配置为合适的上拉/下拉。)
Spinlock-Safe GPIO访问
-------------------------
大多数GPIO控制器可以使用内存读写指令访问。它们不需要休眠,且可以从内部硬件中断处理(非线程)和类似的上下文环境安全完成。
使用下列调用访问这些GPIO,此时gpio_cansleep将总是返回错误
/* GPIO INPUT: return zero or nonzero */
int gpio_get_value(unsigned gpio);
/* GPIO OUTPUT */
void gpio_set_value(unsigned gpio, int value);
其中,value是一个布尔型参数,零表示低,非零表示高。当读一个输出管脚的值时,返回的值应该是在管脚上看到的值。。。这并不总是与指定输出值相匹配的,因为存在开漏信号和输出延迟问题。
get/set调用没有错误返回,因为“无效GPIO”应该已经由gpio_direction_*()提早报告了。虽然如此,并非所有的平台都可以读取输出管脚的值,那些不能读的应该总是返回零。同时,对那些可能导致睡眠的GPIO使用这些接口是一个错误。
平台的特定实现被鼓励优化这两个调用以获取GPIO值。在那些GPIO号码是常量的情况下,它们通常只需一对指令(读或写一个硬件寄存器)访问,且不需要spinlock。这样的优化可以使位拆分应用更有效率(在时间和空间上)(相比较于花费一堆指令在子例程调用来说)。
评论
查看更多