收藏 分享(赏)

嵌入式ppt第八章.ppt

上传人:bubibi 文档编号:20014193 上传时间:2023-12-02 格式:PPT 页数:149 大小:1.76MB
下载 相关 举报
嵌入式ppt第八章.ppt_第1页
第1页 / 共149页
嵌入式ppt第八章.ppt_第2页
第2页 / 共149页
嵌入式ppt第八章.ppt_第3页
第3页 / 共149页
嵌入式ppt第八章.ppt_第4页
第4页 / 共149页
嵌入式ppt第八章.ppt_第5页
第5页 / 共149页
亲,该文档总共149页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

1、第第8章章设备驱动程序设计设备驱动程序设计目目录录8.1 8.1 设备驱动程序开发概述设备驱动程序开发概述8.2 8.2 内核设备模型内核设备模型8 8.3.3 字符设备驱动设计框架字符设备驱动设计框架8.4 8.4 GPIOGPIO驱动概述驱动概述8.5 8.5 IICIIC总线驱动设计总线驱动设计8.6 8.6 块设备驱动程序设计概述块设备驱动程序设计概述8.7 8.7 嵌入式网络设备驱动设计嵌入式网络设备驱动设计设备驱动程序是应用程序和硬件设备之间的一个软件层,它向下负责和硬件设备的交互,向上通过一个通用的接口挂接到文件系统上,从而使用户或应用程序可以无需考虑具体的硬件实现环节。由于设备

2、驱动程序为应用程序屏蔽了硬件细节,在用户或者应用程序看来,硬件设备只是一个透明的设备文件,应用程序对该硬件进行操作就像是对普通的文件进行访问和控制硬件设备(如打开、关闭、读和写等)。作为Linux内核的重要组成部分,设备驱动程序主要完成以下的功能:(1)对设备初始化和释放。(2)把数据从内核传送到硬件和从硬件读取数据。(3)读取应用程序传送给设备文件的数据和回送应用程序请求的数据。(4)检测错误和处理中断。设备驱动程序开发概述设备驱动程序开发概述Part One8.18.1图8-1驱动层次结构图Linux设备驱动程序可以分为两个主要组成部分:(1)对子程序进行自动配置和初始化,检测驱动的硬件设

3、备是否正常,能否正常工作。(2)设备服务子程序和中断服务子程序,这两者分别是驱动程序的上下两部分。驱动上部分即设备服务子程序的执行是系统调用的结果,并且伴随着用户态向核心态的演变,在此过程中还可以调用与进程运行环境有关的函数,比如 sleep()函数。驱动程序的下半部分即中断服务子程序。8.1.1Linux设备驱动程序分程序分类1.字符设备字符设备字符设备是传输数据以字符为单位进行的设备,字符设备驱动程序通常实现open、close、read和write等系统调用函数,常见的字符设备有键盘、串口、控制台等。通过文件系统节点可以访问字符设备,例如/dev/tty1和/dev/lp1。字符设备和普

4、通文件系统之间唯一的区别是普通文件允许往复读写,而大多数字符设备驱动仅是数据通道,只能顺序读写。此外,字符设备驱动程序不需要缓冲且不以固定大小进行操作,它与用户进程之间直接相互传输数据。2块设备块设备所谓块设备是指对其信息的存取以“块”为单位。如常见的光盘、硬磁盘、软磁盘、磁带等,块长大小通常取512B、1024B或4096B等。块设备和字符设备一样可以通过文件系统节点来访问。在大多数linux系统中,只能将块设备看作多个块进行访问,一个块设备通常是1024B数据。块设备的特点是对设备的读写是以块为单位的,并且对设备的访问是随机的。块设备和字符设备的区别主要在于内核内部的管理上,其中应用程序对

5、于字符设备的每个I/O操作都会直接传递给系统内核对应的驱动程序;而应用程序对于块设备的操作要经过系统的缓冲区管理间接地传递给驱动程序处理。3.网络设备网络设备网络设备驱动通常是通过套接字(Socket)等接口来实现操作。任何网络事务处理都可以通过接口来完成和其他宿主机数据的交换。内核和网络设备驱动程序之间的通信与字符设备驱动程序和块设备驱动程序与内核的通信是完全不同的。8.1.2驱动程序的处理过程驱动程序的处理过程如果逻辑I/O层请求读取块设备的第j块,假设请求到来时驱动程序处于空闲状态,那么驱动程序立刻执行该请求,由于外设速度相比CPU要慢很多,因此进程会在该数据块缓存上阻塞,并调度新的进程

6、运行。但是如果驱动程序同时正在处理另一个请求,那么就将请求挂在一个请求队列中,对应的请求进程也阻塞于所请求的数据块。当完成一个请求的处理时,设备控制器向系统发出一个中断信号。结束中断的处理方法是将设备控制器和通道的控制块均置为空闲状态,然后查看请求队列是否为空。如果为空则驱动程序返回,反之则继续处理下一个请求。如果传输错误,则向系统报告错误或者进行相应进程重复执行处理。对于故障中断,则向系统报告故障,由系统进一步处理。1.内存与内存与I/O端口端口编写驱动程序大多数情况下其本质都是对内存和I/O端口的操作。(1)内存Linux通常有以下几种地址类型:用户虚拟地址物理地址总线地址内核逻辑地址内核

7、虚拟地址(2)I/O端口有两个重要的内核调用可以保证驱动程序使用正确的端口,它们定义在include/linux/ioport.h中。int_check_region(structresource*,resource_size_t,resource_size_t);该函数的作用是查看系统I/O表,看是否有别的驱动程序占用某一段I/O口。structresource*_request_region(structresource*,resource_size_tstart,resource_size_tn,constchar*name,intflags);根据CPU系统结构的不同,CPU对I/O端

8、口的编址方式通常有两种:第一种是 I/O 映射方式,如 x86 处理器为外设专门实现了一个单独的地址空间,称为 I/O地址空间,CPU 通过专门的 I/O 指令来访问这一空间的地址单元;第二种是内存映射方式,RSIC 指令系统的 CPU(如 ARM、Power PC等)通常只实现一个物理地址空间,外设 I/O 端口成为了内存的一部分,此时 CPU 访问 I/O 端口就像访问一个内存单元,不需要单独的 I/O 指令。这两种方式在硬件实现上的差异对软件来说是完全可见的。2.并发控制并发控制在驱动程序中经常会出现多个进程同时访问相同的资源时可能会出现竞态(racecondition),即竞争资源状态

9、,因此必须对共享资料进行并发控制。Linux内核中解决并发控制最常用的方法是自旋锁(spinlocks)和信号量(semaphores)。(1)自旋锁自旋锁是一个互斥现象的设备,它只能是两个值:locked(锁定)或unlocked(解锁)。它通常作为一个整型值的单位来实现。在任何时刻,自旋锁只能有一个保持者,也就是说在同一时刻只能有一个进程获得锁。(2)信号量信号量是一个结合一对函数的整型值,这对函数通常称为P操作和V操作。自旋锁和信号量有很多相似之处但又有些本质的不同。其相同之处主要有:首先它们对互斥来说都是非常有用的工具;其次在任何时刻最多只能有一个线程获得自旋锁或信号量。不同之处主要有

10、:首先自旋锁可在不能睡眠的代码中使用,如在中断服务程序(ISR)中使用,而信号量不可以;其次自旋锁和信号量的实现机制不一样;最后通常自旋锁被用在多处理器系统。总体而言,自旋锁通常适合保持时间非常短的情况,它可以在任何上下文中使用,而信号量用于保持时间较长的情况,只能在进程上下文中使用。3阻塞与非阻塞阻塞与非阻塞在驱动程序的处理过程中我们提到了阻塞的概念,这里进行以下说明。阻塞(blocking)和非阻塞(nonblocking)是设备访问的两种不同模式,前者在I/O操作暂时不可进行时会让进程睡眠,而后者在I/O操作暂时不可进行时并不挂起进程,它或者放弃,或者不停地查询,直到可以进行操作为止。(

11、1)阻塞与非阻塞操作阻塞操作是指在执行设备操作时,若不能获得资源则进程挂起,直到满足可操作的条件再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列中移走,直到等待条件被满足。非阻塞操作是在不能进行设备操作时并不挂起,它会立即返回,使得应用程序可以快速查询状态。(2)异步通知异步通知是指一旦设备准备就绪,则该设备会主动通知应用程序,这样应用程序就不需要不断地查询设备状态,通常把异步通知称为信号驱动的异步I/O(SIGIO),这有点类似于硬件上的中断。4中断处理中断处理与Linux设备驱动程序中断处理相关的函数首先是申请和释放IRQ(中断请求)函数,即request_irq和free_ir

12、q,这两个重要的中断函数原型如下,在头文件include/linux/interrupt.h中声明。申请函数原型如下:intrequest_irq(unsignedintirq,void(*handler)(int,void*,structpt_regs*),unsignedlongfrags,constchar*device,void*dev_id);(2.4内核中)request_irq(unsignedintirq,irq_handler_thandler,unsignedlongflags,constchar*name,void*dev);(2.6内核及以后)释放函数原型如下:void

13、free_irq(unsignedintirq,void*dev_id);(2.4版本)voidfree_irq(unsignedintirq,void*dev);(2.6版本及以后)该函数的作用是释放一个IRQ,一般是在退出设备或关闭设备时调用Linux将中断分为两个部分:上半部分(tophalf)和下半部分(bottomhalf)上半部分的功能是注册中断上半部和下半部最大的不同是下半部是可中断的,而上半部是不可中断的,会被内核立即执行,下半部完成了中断处理程序的大部分工作,所以通常比较耗时,因此下半部由系统自行安排运行,不在中断服务上下文执行。从2.3版本开始,Linux为实现下半部的机制

14、主要引入了tasklet()和软中断。软中断是一组静态定义的下半部接口,可在所有处理器上同时执行-这就要求软中断执行的函数必须可重入。当软中断在访问临界区时需要用到同步机制,如自旋锁。软中断主要针对时间严格要求的下半部使用,如网络和SCSI。Tasklet是基于软中断实现的,比软中断接口简单,同步要求较低,大多数情况下都可以使用tasklet。Tasklet是一个可以在由系统决定的安全时刻在软件中断上下文被调用运行的特殊机制,它可以被多次调用运行,但是tasklet的函数调用并不会积累,也就是说只会运行一次。下面是tasklet的定义:structtasklet_structstructtas

15、klet_struct*next;/指向下一个taskletunsignedlongstate;/tasklet的状态atomic_tcount;/计数,1表示禁止void(*func)(unsignedlong);/处理函数指针unsignedlongdata;/处理函数参数;在interrupt.h中可以看到tasklet的数据结构,其状态定义了两个位的含义:enumTASKLET_STATE_SCHED,/*正在运行*/TASKLET_STATE_RUN/*已被调度,准备运行*/;工作队列(workqueue)接口在linux2.5版本中引入,取代了任务队列接口。工作队列与tasklet

16、的主要区别在于tasklet在软中断上下文中运行,代码必须是原子的;工作队列函数在一个内核线程上下文中运行,并且可以在延迟一段时间后才执行,因而具有更多的灵活性,并且工作队列可以使用信号量等能够sleep的函数。另外,工作队列的中断服务程序和tasklet非常类似,唯一不同就是它调用schedule_work()来调度下半部处理,而tasklet使用tasklet_schedule()函数来调度下半部处理。驱动程序在使用工作队列时的主要步骤是:(1)当驱动程序不使用默认的工作队列时,驱动程序可以创建一个新的工作队列。(2)当驱动程序需要延迟时,根据需要静态或者动态创建工作队列。(3)将工作队列

17、任务插入工作队列。5.设备号设备号用户进程与硬件的交流是通过设备文件进行的,硬件在系统中会被抽象成为一个设备文件,访问设备文件就相当于访问其所对应的硬件。每个设备文件都有其文件属性(c/b),表示是字符设备还是块设备。每个设备文件的设备号有两个:第一个是主设备号,标识驱动程序对应一类设备的标识;第二个是从设备号,用来区分使用共用的设备驱动程序的不同硬件设备。在linux2.6内核中,主从设备被定义为一个dev_t类型的32位数,其中前12位表示主设备号,后20位表示从设备号。另外,在include/linux/kdev.h中定义了如下的几个宏来操作主从设备号。#defineMAJOR(dev)

18、(unsignedint)(dev)MINORBITS)#defineMINOR(dev)(unsignedint)(dev)&MINORMASK)#defineMKDEV(ma,mi)(ma)refcount,1)Ktype域是一个指向kobj-type结构的指针,表示该对象的类型。Ktype的定义如下:structkobj_typevoid(*release)(structkobject*kobj);/conststructsysfs_ops*sysfs_ops;structattribute*default_attrs;kobject通常通过kset组织成层次化的结构,kset是具有相同

19、类型的kobject的集合Kset的定义如下:structksetstructlist_headlist;/用于连接该kset中所有kobject的链表头spinlock_tlist_lock;/迭代时用的锁structkobjectkobj;/指向代表该集合基类的对象conststructkset_uevent_ops*uevent_ops;/指向一个用于处理集合中kobject对象的热插拔结构操作的结构体;8.2.4 设备模型的模型的组织-platform总线Platform总线就是从2.6内核开始引入的一种虚拟总线,主要用来管理CPU的片上资源,具有更好的移植性。目前,大部分的驱动都是用

20、Platform总线编写的,除了极少数情况之外如构建内核最小系统之内的而且能够采用CPU存储器总线直接寻址的设备。Platform总线模型主要包括platform_device、platform_bus、platform_driver三个部分。设备是连接在总线上的物理实体,是硬件设备的具体描述,在linux内核中以structdevice结构进行描述,该结构体定义在include/linux/device.h中。具有相同功能的设备被归为一类(class)。驱动程序在前文已经介绍过,是操作设备的软件接口。所有的设备都必须要有配套的驱动程序才能正常工作。反过来说,一个驱动程序可以驱动多个设备。驱动

21、程序通过include/linux/device.h中的structdevice_driver描述。由于内核驱动框架的不断发展,已经提供了一些常用具体设备的具有共性的程序源码,使得普通用户在开发时可以直接使用或者进行修改后就可以开发出目标程序,十分便捷。同时实际上在普通开发者进行驱动程序开发的时候并不直接使用bus、device和driver,而是使用它们的封装函数。Platform总线模型的platform_driver机制将设备的本身资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过标准接口进行申请和使用,具有很高的安全性和可靠性。而模型中的platform_device是一个

22、具有自我管理功能的子系统。当platform模型中总线上有设备,又有驱动的时候,就会进行设备与驱动匹配的过程,总线起到了沟通设备和驱动的桥梁作用。1.Platformbus初始化初始化Platform总线的初始化是在/drivers/base/platform.c中的platform_bus_init()完成的,代码如下:int_initplatform_bus_init(void)interror;early_platform_cleanup();error=device_register(&platform_bus);if(error)returnerror;error=bus_regis

23、ter(&platform_bus_type);if(error)device_unregister(&platform_bus);returnerror;这段初始化代码调用device_register向内核注册(创建)了一个名为“platform_bus”的设备.后续platform的设备都会以此为parent。在sysfs中表示为所有platform类型的设备都会添加在platform_bus所代码的目录下/sys/devices/platform。然后这段初始化代码又调用bus_register注册了platform_bus_type2.platformdevice注册注册在最底层,L

24、inux系统中的每一个设备都是由一个device数据结构来代表的,该结构定义在中。device结构体用于描述设备相关的信息设备之间的层次关系,以及设备与总线、驱动的关系。Platform_device是对device的封装。Platform设备通过structplatform_device来进行描述。structplatform_deviceconstchar*name;/平台设备的名称intid;/设备的ID,当ID=-1的时候,表示设备名称只有一个,否则表示设备编号structdevicedev;u32num_resources;structresource*resource;consts

25、tructplatform_device_id*id_entry;/*MFDcellpointer*/structmfd_cell*mfd_cell;/*archspecificadditions*/structpdev_archdataarchdata;3.platformdriver的注册的注册系统中的每个驱动程序由一个device_driver对象描述。Platform设备是一种特殊的设备,它与处理器是通过CPU地址数据控制总线或者GPIO连接的。Platform_driver既具有一般device的共性,也有自身的特殊属性。Platform_driver的描述如下所示:structpl

26、atform_driverint(*probe)(structplatform_device*);/指向设备探测函数int(*remove)(structplatform_device*);/指向设备移除函数void(*shutdown)(structplatform_device*);/指向设备关闭函数int(*suspend)(structplatform_device*,pm_message_tstate);/指向设备挂起函数int(*resume)(structplatform_device*);/指向设备恢复函数structdevice_driverdriver;/驱动基类const

27、structplatform_device_id*id_table;/平台设备id列表;字符设备驱动设计框架字符设备驱动设计框架Part Three8.38.38.3.1 字符字符设备的重要数据的重要数据结构构字符设备驱动程序编写通常都要涉及到三个重要的内核数据结构,分别是file_operations结构体、file结构体和inode结构体。File_operations为用户态应用程序提供接口,是系统调用和驱动程序关联的重要数据结构。File结构体在内核代码include/linux/fs.h中定义,表示一个抽象的打开的文件,file_operations结构体就是file结构的一个成员。

28、Inode结构表示一个文件,而file结构表示一个打开的文件。这正是二者间最重要的关系。每个进程为每个打开的文件分配一个文件描述符,每个文件描述符对应一个file结构,同一个文件被不同的进程打开后,在不同的进程中会有不同的file文件结构,其中包括了文件的操作方式(只读只写读写),偏移量,以及指向inode的指针等等。这样,不同的file结构指向了同一个inode节点。这里介绍一下字符设备的分配和初始化,它有两种不同的方式。cdev_alloc()函数用于动态分配一个新的cdev结构体并初始化。一般如果建立新的cdev结构体可以使用该方式,这里给出一个参考代码:structcdev*my_cd

29、ev=cdev_alloc();my_cdev-owner=THIS_MODULE;my_cdev-ops=&fops;如果需要把cdev结构体嵌入到指定设备结构中,可以采用静态分配方式。cdev_init()函数可以初始化一个静态分配的cdev结构体,并建立cdev和file_operation之间的连接。与cdev_alloc()唯一不同的是,cdev_init()函数用于初始化已经存在的cdev结构体。这里给出一段参考代码:structcdevmy_cdev;cdev_init(&my_cdev,&fops);my_cdev.owner=THIS_MODULE;8.3.2字符设备驱动框架

30、字符设备驱动框架字符设备驱动程序的初始化流程一般可以用如下的过程来表示:(1)定义相关的设备文件结构体(如file_operation()中的相关成员函数的定义)。(2)向内核申请主设备号(建议采用动态方式)。(3)申请成功后,通过调用MAJOR()函数获取主设备号。(4)初始化cdev的结构体,可以通过调用cdev_init()函数实现。(5)通过调用cdev_add()函数注册cdev到内核。(6)注册设备模块,主要使用module_init()函数和module_exit()函数。编写一个字符设备的驱动程序,首先要注册一个设备号。内核提供了三个函数来注册一组字符设备编号,这三个函数分别是

31、:alloc_chrdev_region()、register_chrdev_region()和register_chrdev()。其中register_chrdev()在上节已经介绍过。这里首先介绍的是alloc_chrdev_region()函数,该函数用于动态申请设备号范围,通过指针参数返回实际分配的起始设备号。Register_chrdev_region()函数用于向内核申请分配已知可用的设备号(次设备号通常为0)范围。下面是该函数原型:intregister_chrdev_region(dev_tfrom,unsignedcount,constchar*name)。参数from是要分

32、配的设备号的dev_t类型数据,表示了要分配的设备编号的起始值,参数count表示了允许分配设备编号的范围。register_chrdev()是一个老版本内核的设备号分配函数,不过新内核对其还是兼容的。Register_chrdev()兼容了动态和静态两种分配方式。Register_chrdev()不仅分配了设备号,同时也注册了设备。这是register_chrdev()与前两个函数的最大区别。也就是说,如果使用alloc_chrdev_region()或register_chrdev_region()分配设备号,还需要对cdev结构体初始化。而register_chrdev()则把对cdev

33、结构体的操作封装在了函数的内部。所以在一般的字符设备驱动程序中,不会看到对cdev的操作。与注册分配字符设备编号的方法类似,内核提供了两个注销字符设备编号范围的函数unregister_chrdev_region()和unregister_chrdev()。这两个函数实际上都调用了_unregister_chrdev_region()函数,原理是一样的。Register_chrdev()函数封装了cdev结构的操作,而alloc_chrdev_region()或register_chrdev_region()只提供了设备号的注册,并未真正的初始化一个设备,只有cdev这个表示设备的结构体初始化

34、了,才可以说设备初始化了。这里举出字符设备驱动程序的常见的两种编程架构。架构一:staticint_initxxx_init(void).register_chrdev(xxx_dev_no,DEV_NAME,&fops);staticvoid_exitxxx_exit(void)unregister_chrdev(xxx_dev_no,DEV_NAME);.module_init(xxx_init);module_exit(xxx_exit);架构二:structxxx_dev_tstructcdevcdev;.xxx_dev;staticint_initxxx_init(void).cde

35、v_init(&xxx_dev.cdev,&xxx_fops);xxx_dev.cdev.owner=THIS_MODULE;alloc_chrdev_region(&xxx_dev_no,0,1,DEV_NAME);ret=cdev_add(&xxx_dev.cdev,xxx_dev_no,1);.staticvoid_exitxxx_exit(void)unregister_chrdev_region(xxx_dev_no,1);cdev_del(&xxx_dev.cdev);.module_init(xxx_init);module_exit(xxx_exit);这两个结构中,前一个应用

36、register_chrdev函数封装了cdev,后面可以直接定义file_operations结构体提供系统调用接口。后一种架构用alloc_chrdev_region注册设备号,然后用cdev_init初始化了一个设备,接着用cdev_add添加了该设备。两种架构在模块卸载函数中,分别用相应的卸载函数实现。当file_operations结构与设备关联在一起后,就可以在驱动的架构中补全file_operations的内容,实现一个完整的驱动架构,比如:staticunsignedintxxx_open()staticunsignedintxxx_ioctl()structfile_oper

37、ationsfops=.owner=THIS_MODULE,.open=xxx_open,.ioctl=xxx_ioctl,/注意新式写法这里应是.unlocked_ioctl=xxx_ioctl;staticint_initxxx_init(void).register_chrdev(xxx_dev_no,DEV_NAME,&fops);staticvoid_exitxxx_exit(void)unregister_chrdev(xxx_dev_no,DEV_NAME);.module_init(xxx_init);module_exit(xxx_exit);GPIO驱动概述驱动概述Part

38、 Foue8.48.4“通用输入输出”GPIO(generalpurposeinputoutput)是嵌入式系统中最简单,最常用的I/O接口。GPIO是一组可编程控制的管脚,由多个寄存器同时控制。通过设置对应的寄存器可以达到设置GPIO口对应状态与功能如读取数据状态,设置输入输出方向,清零,中断使能等功能。GPIO的驱动主要作用就是读取GPIO口的内容,或者设置GPIO口的状态。GPIO是与硬件体系密切相关,在linux内核目录下的相关文件中我们可以发现针对不同硬件芯片的GPIO定义和使用方法,如本书涉及的S5PV210芯片linux内核中也有相应的驱动程序支持(如在/drivers/gpio

39、/)。当然,linux内核也提供了一个模型框架,能够使用统一的接口来操作GPIO,这个架构被称作gpiolib,系统通过gpiolib.c文件来描述该架构。说明文档可见Documention/gpio.txt。8.4.1 gpiolib关关键数据数据结构构Gpiolib架构下最重要的数据结构是gpio_chip结构体和gpio_desc结构体。gpio_chip结构体的部分定义如下:Structgpio_chip.int(*request)(structgpio_chip*chip,unsignedoffset);/申请gpio资源;void(*free)(structgpio_chip*ch

40、ip,unsignedoffset);/释放gpio资源;int(*direction_input)(structgpio_chip*chip,unsignedoffset);/设置GPIO口方向的操作int(*get)(structgpio_chip*chip,unsignedoffset);int(*direction_output)(structgpio_chip*chip,unsignedoffset,intvalue);/设置GPIO口方向的操作int(*set_debounce)(structgpio_chip*chip,unsignedoffset,unsigneddebounc

41、e);void(*set)(structgpio_chip*chip,unsignedoffset,intvalue);/设置GPIO口高低电平值操作int(*to_irq)(structgpio_chip*chip,unsignedoffset);void(*dbg_show)(structseq_file*s,structgpio_chip*chip);intbase;u16ngpio;.;8.4.2 GPIO的申的申请和注册和注册GPIO的申请在gpiolib架构下是通过gpio_request_array函数实现的。这里的申请的主要标识就是检测GPIO描述符desc-flags的FLA

42、G_REQUESTED标识,如果已申请该标识是1,否则是0,往往多个gpio会作为一个数组来进行申请。当然,如前文所述,用户也可以使用linux系统已经支持的芯片GPIO接口驱动来完成目标系统设计。IIC总线驱动设计总线驱动设计Part Five8.58.58.5.1 IIC 总线概述概述IIC(inter-integratedcircuit)总线是由菲利浦公司开发的一种同步串行总线协议,用于连接微控制器及其外围设备。I2C总线在传送数据过程中共有3种类型信号,它们分别是起始信号、终止信号和应答信号起始信号与终止信号应答信号图典型的I2C通信数据帧格式所有的IIC总线上的数据帧格式均有如下这些

43、特点:(1)无论何种方式,起始停止,寻址字节都由主控器发送,数据字节的传送方向则遵循寻址字节中的方向位的规定。(2)寻址字节只表明器件地址及传送方向,器件内部的N个数据地址由器件设计者在该器件的IIC总线数据操作格式化中指定第一个数据字节作为器件内的单元地址(SIBADR)数据,并且设置地址自动加减功能,以减少单元地址寻址操作。(3)每个字节传送都必须有应答信号相随。(4)IIC总线被控器在接收到起始信号后都必须复位它们的总线逻辑,以便对将要开始的被控器地址的传送进行预处理。8.5.2 IIC 驱动程序框架程序框架Linux内核的I2C总线驱动程序框架如图8-7所示。I2C总线驱动程序主要由3

44、个部分组成:I2Ccore(I2C核心),adapter(适配器),client(设备驱动)。(1)I2Ccore(I2C核心)是I2C总线驱动程序体系结构的核心,它为总线设备驱动提供统一的接口,通过这些接口来访问在特定I2C设备驱动程序中实现的功能,并实现从I2C总线驱动体系结构中添加和删除总线驱动的方法等。(2)adapter部分代表I2C适配器驱动,adapter是各个适配器驱动所构成的集合,主要实现各相应适配器数据结构I2C_adapter的具体的通信传输算法(I2C_algorithm),此算法管理I2C控制器及实现总线数据的发送接收等操作。(3)Client部分则代表挂载在I2C总

45、线上的设备驱动,Client部分是各个I2C设备构成的集合,主要实现各描述I2C设备的数据结构I2C_client及其私有部分,并通过I2Ccore提供的接口实现设备的注册,提供设备可使用的地址范围及地址检测成功后的回调函数。处于控制中心的I2Ccore实现了控制策略,具体I2C总线的适配器和设备的驱动实现了具体设备可用的机制,控制策略和底层机制通过中间的函数接口相联系。正是中间的函数接口使得控制策略与底层机制无关,从而使得控制策略具有良好的可移植性和重用性。在实际设计中,实际设计中,I2C核心提供的接口不需要修改,只需核心提供的接口不需要修改,只需针对目标总线适配器驱动和设备驱动进行必要修改

46、即可。针对目标总线适配器驱动和设备驱动进行必要修改即可。8.5.3 关关键数据数据结构构1I2C适配器适配器一个I2C适配器对应I2C_adapter结构体。I2C_adapter是对硬件上的适配器的抽象,相当于整个I2C驱动的控制器。它的作用就是产生总线时序。I2C_adapter数据结构描述如下:structI2C_adapterstructmodule*owner;unsignedintclass;/*classestoallowprobingfor*/conststructI2C_algorithm*algo;/*thealgorithmtoaccessthebus*/void*alg

47、o_data;/*datafieldsthatarevalidforalldevices*/structrt_mutexbus_lock;inttimeout;/*injiffies*/intretries;structdevicedev;/*theadapterdevice*/intnr;charname48;structcompletiondev_released;structmutexuserspace_clients_lock;structlist_headuserspace_clients;I2C_algorithm正是提供了控制适配器产生总线时序的函数。I2C_algorithm的

48、定义如下:structI2C_algorithmint(*master_xfer)(structI2C_adapter*adap,structI2C_msg*msgs,intnum);/I2C传输函数指针int(*smbus_xfer)(structI2C_adapter*adap,u16addr,unsignedshortflags,charread_write,u8command,intsize,unionI2C_smbus_data*data);/smbus传输函数指针u32(*functionality)(structI2C_adapter*);该结构体中master_xfer和smb

49、us_xfer函数十分重要,它们分别是I2C和SMbus的传输函数。如master_xfer()用于产生以I2C_msg(I2C消息)为单位的I2C访问周期需要的信号。I2C_msg结构体定义如下:structI2C_msg_u16addr;/从设备地址_u16flags;/标志位#defineI2C_M_TEN0 x0010#defineI2C_M_RD0 x0001#defineI2C_M_NOSTART0 x4000#defineI2C_M_REV_DIR_ADDR0 x2000#defineI2C_M_IGNORE_NAK0 x1000#defineI2C_M_NO_RD_ACK0 x

50、0800#defineI2C_M_RECV_LEN0 x0400_u16len;/缓冲区数据字节数_u8*buf;/数据缓冲区,从设备读入或者写数据到设备中;2I2C_client与适配器对应的是从设备,其对应的数据结构是I2C_client。每一个I2C设备都需要一个I2C_client来描述。通常建议在内核空间编写I2C从设备的驱动程序。I2C_client有如下定义:structI2C_clientunsignedshortflags;/标志unsignedshortaddr;/芯片地址/*addressesarestoredinthe*/charnameI2C_NAME_SIZE;/设

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 网络技术 > 前端技术

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:文库网官方知乎号:文库网

经营许可证编号: 粤ICP备2021046453号世界地图

文库网官网©版权所有2025营业执照举报