通用I/O接口(General Purpose IO Interface,GPIO)是ARM系统及片上SoC(System-on-Chip)系统中非常重要的一种I/O接口,具有使用灵活、可配置性好、硬件代价小等优点,在ARM系统中广泛应用。
1 实例说明
ARM Linux系统的设备可分为字符设备、块设备和网络设备三种。GPIO属于字符设备,字符设备是指存取时具有较少缓存的设备。而块设备的读写都由缓存来支持,并且块设备必须能够随机存取,字符设备则没有这个要求。
GPIO不仅支持常用的中断和查询低速数据传输方式,还支持DMA高速数据传输方式,支持多种I/O端口类型,可满足多种应用需求。
本章介绍ARM的通用I/O资源,并结合外围电路给出了这些GPIO的一般使用方法。
2 GPIO原理
2.1 GPIO设备驱动原理
在Linux系统下,字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件。I/O操作就紧接着发生了。块设备则不然,它利用
一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据;如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁
盘等慢速设备设计的,以免耗费过多的CPU时间来等待。
GPIO属于字符设备,其驱动程序的使用可以按照两种方式编译,一种是静态编译进内核,另种是编译成模块以供动态加载。由于嵌入式Linux支持静态编译和动态加载两种模式,如果考虑到精简内核的需要,这里可以使用动态加载的方法来实现驱动的装载。
设备驱动程序必须向Linux核心或者它所在的子系统提供个标准的接口。例如,USB驱动程序向Linux核心提供了一个设备文件I/O接口,GPIO设备驱动程序向GPIO子系统提供了GPIO设备接口,接着向核心提供了文件I/O和缓冲区的接口。
1. 用户程序访问GPIO
用户程序访问设备的整体工作情况如图8-1所示。
![通用I/O模块(GPIO)设计_休闲](http://blog.eccn.com/UploadFiles/2007-4/44374078.jpg)
如
图8-l所示的数据结构是在内核态工作,而内核通过对相应数据结构的赋值,以此记录了用户程序对外部设备的使用情况。节点数据结构工作在JFFS(一种基
于Flash的文件系统)阶段,其定位了用户程序访问的设备文件,并根据相应文件属性满足用户程序对该设备文件的访问。在对设备数据的交换过程
中,File数据结构维护着缓冲区的数据。
设备驱动程序使用标准的核心服务如内存分配、中断转发和等待队列来完成工作。大多数设备驱动程序可以在需要的时候作为核心模块加载,在不需要的时候卸载。这使得核心服务对于系统资源非常具有适应性和效率性。
系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在
应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它具有以下功能。
·对设备初始化和释放。
·把数据从内核传送到硬件,以及从硬件读取数据。
·读取应用程序传送给设备文件的数据,以及回送应用程序请求的数据。
·检测和处理设备出现的错误。
2.用户进程调用
用户进程通过设备文件与实际的硬件打交道。每个设备文件都有其文件属性(c/b),表示是字符设备还是块设备。另外,每个文件都有两个设备号,第一个是主
设备号,标识驱动程序,第二个是次设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用次设备号来区分它们。设备文件的主设备
号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。
在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在驱动程序的子函数返回后才能进行其他的工作。如果驱动程序陷入死循环,那么整个内核就会崩溃,只有重新启动系统。
2.2如何驱动GPIO及对应设备
一般的ARM都提供超过100路的GPIO复用管脚,要使用这些管脚,就必须首先将其驱动起来。如果使用的芯片带有MMU内存管理,那么在写驱动模块的时候不能直接去操作物理地址,必须利用io rein ap命令重新映射。
1. I/0端口
和硬件打交道离不开I/O端口,老的ISA设备经常是占用实际的I/0端口,在Linux下,操作系统没有对I/0端口屏蔽。也就是说,任何驱动程
序都可以对任意的I/O;端口操作,这样就很容易引起混乱。每个驱动程序都应该自己避免误用端口,有两个重要的kernel函数可以保证驱动程序做到这一
点。
check-region(int i0-port, int 0ff_set)
这个函数用来查看系统的I/0表,看是否有别的驱动程序占用某一段I/0口。参数1:I/0端口的基地址;参数2:I/O端口占用的范围。返回值:0,没有占用;非0,已经被占用。
request_region(int i0_port, inL 0ff_set,char★deVname)
如果这段:I/0端口没有被占用,那么在驱动程序中就可以使用它。在使用之前,必须向系统登记,以防止被其他程序占用;登记后,在/proc
/ioports文什中可以看到登记的I/O口。参数l:I/O端口的基地址;参数2:I/O端口占用的范围;参数3:使用这段I/0地址的设备名。在对
I/O口登记后,就可以放心地用inb(),outb()之类的函数来访问了。
2. 和设备文件对话
驱动程序提供了对设备操作的接口,同时在程序中实现了基本操作所需要的基本函数。用户程序通过访问设备文件的方式对设备间接操作,Linux系统提供了ioctl(input outputcontrol的缩写)函数可以很方便地实现这一操作。
其中,fd就是用户程序打开设备时使用open函数返回的文件标识符;cmd就是用户程序对设备的控制命令;后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。
选择设备的控制命令时,需要根据Linux文档所提供的标准控制字(Documentation/ioctl-number.txt)中选择的合适控制字,如果选择不合适会和系统中的其他设备发生冲突。
3 GPlO应用举例
GPIO的硬件应用如图8-2所示,它可以多路复用,以在需要时提供额外的功能。但GPIO的许多引脚是和地址线、数据线、串口线等引脚复用的。
![通用I/O模块(GPIO)设计_职场_02](http://blog.eccn.com/UploadFiles/2007-4/44627642.jpg)
4 GPlO设备程序开发
4.1开发步骤
GPIO驱动可以归类为Linux设备驱动的字符设备驱动,以下是开发它的一些具体步骤。
(1)模块化驱动程序
不失Linux驱动开发的一般性,在写字符设备的驱动程序时,也要遵守模块化编程的一般规范。设备模块在用户空间的初始化和终止:
·Init_mnodule()向内核注册模块提供数据结构、局部和全局变量。
·Cleanup_module()取消所有init_module在内核中的注册。
(2)设备模块在内核空间的内存申请和释放
kmalloc()函数分配一段内存,这样就实现了Chrdevs向量表中指向设备驱动程序名称的指针。使用kfree释放内存。
(3)字符设备主设备号和次设备号的分配
主设备号标志设备对应的驱动程序,内核利用主设备号将设备与相应的驱动程序对应起来。主设备号的取值范围是0~255,如果不善加规划,则容易造成主设备号的冲突。主设备号的分配有静态和动态之分,从开发设备的角度来看,推荐静态设备号的分配。
次设备号由驱动程序使用,内核的其他部分并不使用它,仅将它传递给驱动程序。一个驱动程序控制若干个设备,次设备号提供了一种区分它们的方法。
(4)设备模块在内核空间的注册与注销
字符设备的注册有2种方法,一种是常用注册方法,通过系统函数registel_chrdev()将设备加入到系统设备列表中;另一种方法是
devfs技术,通过系统函数devfs-regisrer()实现设备的注册。注销与注册相反,分别调用unregister_chrdev()函数和
devfs_unregister()函数。
(5)设备模块在内核空间提供系统调用的函数
设备驱动程序在注册成功以后,用户可以通过访问设备特殊文件(一般情况在/dev目录下)实现系统调用。
GPIO驱动程序只需要一个file_operations数据结构体就可以了。这是因为GPIO本身在设计阶段就定制好了接入的物理设备,接入到
GPIO端口的物理设备并不需要即插即用。因此可以在开发阶段定制该接口的物理设备,也可以把这个接口的设备作为该产品的标配外设发布。
这样,在编写驱动程序的过程中,只要写好应用程序使用标准系统调用打开、读取、写和关闭等处理函数,就可以完成驱动程序的开发。其中,标准系统调用处理函数在所有的字符设备里面具有同样的功能。
4.2 GPlO端口编程
以下程序的运行效果为使接在GPIO口的LED显示器轮流被点亮。
5实例总结
本章介绍了ARM的GPIO资源,并结合ARM的外围电路给出了这些GPIO的一般使用方法。GPIO为外围设备提供了信号输出和从外围设备输入信号到
ARM的引脚。这些引脚能通过软件提供多用途的输入和输出信号。另外,ARM提供多路GPIO复用管脚,要使用这些管脚,就必须首先将其驱动起来。