嵌入式Linux中I2C设备驱动程序的研究与实现

来源:互联网 发布:食品经营许可证 知乎 编辑:程序博客网 时间:2024/04/29 13:03

 

I2CInter Integrated Circuit Bus的缩写,中文译成内部集成电路总线它是Philips 公司于20 世纪80 年代研发成功的一种具有多端控制功能的双线双向串行数据总线标准其具有模块化、电路结构简单等优点。在嵌入式系统中,I2C总线已经成为器件接口的标准之一常用于连接RAMEEPROM 以及LCD 控制器等设备。另外,总线的数据传输是以字节为单位的。

目前,标准的I2C的传输速率可以达到100kbit/s,能支持128 个设备,增强型I2C传输速率可达400kbit/s,能支持多达1024 个设备,高速模式下的I2C 传输速率更高达3.4Mbit/s

 序就可以轻松地操作和驱动硬件架构的分层。

2 Linux I2C 体系结构

2.1 Linux I2C 体系结构分析

Linux I2C 体系结构由大部分组成:

(1)I2C框架:I2C.h I2C-core.c I2C框架的主体,提供了核心数据结构的定义、I2C 适配器驱动和设备驱动的注册、注销方法,I2C 通信方法(algorithm)上层的、与具体适配器无关的代码、以及检测设备地址的上层代码等。作为核心的I2C-core.c 还为总线驱动设备提供了一些统一的调用接口进行读写和设置操作另外它还提供了将各种支持的总线设备驱动添加到这个体系中的方法以及当不再使用这些总线驱动时从体系中删除的方法。

(2)I2C 总线驱动I2C总线驱动是对I2C 硬件体系结构中适配器端的实现,I2C 总线驱动主要包含了I2C 适配器数据结构I2C_adapter, 以及描述在具体I2C 适配器上的总线通信方法i2c_algorithm 数据结构。

(3)I2C 设备驱动:I2C 设备驱动是对I2C 硬件体系结构中设备端的实现, 设备一般挂接在受CPU 控制的I2C 适配器上通过I2C 适配器与CPU 交换数据。I2C 设备驱动主要包含了数据结构i2c_driver i2c_client

这三部分的关系如图所示。

1Linux I2C 体系结构

2.2 I2C驱动程序中的重要数据结构

I2C 框架的i2c.h 这个头文件中对个关键的结构体进行了定义它们分别是i2c_adapteri2c_algorithmi2c_driver i2c_client。结构体i2c_adapter 是一个I2C控制器的逻辑抽象,并且作为最核心的数据结构提供了I2C适配器的驱动。i2c_algorithm对应一套通信方法其封装了对一个I2C 控制器的读写操作并且提供的通信函数可以控制适配器上产生特定的访问周期,这套通信方法由驱动开发者来完成。i2c_driver 则是对应于一套驱动方法,用于辅助作用的数据结构,不对应任何物理实体,仅是提供了I2C 设备i2c_client 的驱动。而i2c_client 对应于真实的物理设备,描述具体设备可能的私有数据结构。

2.3I2C驱动程序中重要数据结构之间的关系

对于上述的个结构体来说其中的i2c_driver i2c_client 是与具体I2C 设备相关的,i2c_adapter i2c_algorithm则共同构成I2C 总线适配器驱动。一个algorithm 可以适用于多个I2C 总线上的不同adapters, 但具体的每个adapter 只能对应于一个algorithm。在i2c_adapter 数据结构中设计了clients指针数组用于记录该总线上每个设备的i2c_client 数据结构。

另外定义内核中全局静态指针数组adapters drivers 分别记录已注册的I2C 适配器驱动和I2C 设备驱动程序。值得注意的是同一个i2c_adapter 中的不同的i2c_client 可能使用同一个i2c_driver,而分属于不同i2c_adapter 中的两个i2c_client 也可能使用同一个i2c_driver

一个具体的I2C 设备驱动程序的开发

AT24C08 是由ATMEL 公司出品的一款EEPROM 存储器。

作为一个标准的I2C 设备AT24C08 个块存储区一个块有256 个数据存储单元,整个AT24C08 具有1024 个存储单元。由于每个数据存储单元可存字节的数据,所以整块AT24C08 的存储能力为1KB

3.1 I2C 设备驱动程序的一般结构及运行流程图

开发一个具体的I2C 设备驱动需要一个完整、标准的结构,而该结构的实现是通过编写两个方面的接口而完成的一个是用以挂接I2C adapter 层来实现对I2C 总线及I2C设备具体的访问方法I2C 核心层的接口主要实现attach_adapter,detach_client,command 等接口函数。另一个是对用户应用层的接口提供用户程序访问I2C设备的接口包括实现open,release,read,write 以及ioctl 等标准文件操作的接口函数。下面将通过对核心层接口和应用层接口的分析来说明I2C 设备驱动程序的运行机制。图I2C 设备驱动程序运行流程图(图中at 代表具体的设备AT24C08):

3.2 I2C 设备驱动的I2C 核心层接口分析

如图的用户空间在通过insmod 命令加载设备驱动程序时设备驱动将通过使用动态模块的方式加载并指向设备初始化函数at_init(),在初始化函数中使用register_chrdev()进行字符型设备的注册并可以通过静态和动态两种方法来申请注册到系统中的设备号。另外将调用核心i2c -core.c 中提供的i2c_add_driver()函数注册由at_driver 数据结构描述的驱动方法,该数据结构中完成了对驱动程序的标示并包含了两个重要的成员函数at_attach_adapter()at_detach_client()

i2c_add_driver () 注册at_driver 数据结构后,at_attach_adapter()函数就会被自动调用,其遍历系统中的每个i2c 总线驱动探测想要访问的设备连接符合i2c driver 特定条件的i2c adapter,并通过i2c adapter 实现对I2C 总线及其设备的访问。

at_attach_adapter()的功能则是依靠调用i2c-core.c 核心中的i2c_probe()函数来实现的,通过i2c_probe()函数可以认领adapter所指向的适配器上的所有合适的设备。设备可能使用的地址由addr_data 数组指出。通过设备地址每次检测到新设备后,i2c_probe()将使用它的第三个参数即回调函数初始化设备的数据结构i2c_client,并用i2c_check_functionality()确定I2C 适配器所支持的通信方法。另外再使用i2c_attach_client()知会I2C 核心系统中包含了一个新的I2C 设备。

通过rmmod 命令对设备驱动进行卸载时在卸载函数at_exit()中将使用i2c_del_driver(),其调用会引起与数据结构at_driver 关联的每个i2c_client 与之解除关联随后at_detach_client()函数也将因此而被调用,at_detach_client()中的i2c_detach_client()又完成与i2c_attach_client()相反的过程,并使用kfree 释放由client 所占的内存。另外卸载函数at_exit()中还将使用unregister_chrdev()对字符型设备进行注销。

3.3I2C设备驱动用户应用层接口分析

在注册字符型设备时设备驱动中初始化了一个structfile_operations 文件操作结构体变量用于链接字符设备驱动程序和用户应用程序,在该结构中定义了一组函数指针。系统就是通过这组函数指针对AT24C08 进行具体的操作,系统首先通过设备文件的主设备号找到相应的设备驱动程序然后读取这个数据结构相应的函数指针,找到相关的功能函数,接着把控制权交给该函数,从而就在上层屏蔽了设备驱动的具体实现细节,提供给用户一个方便快捷的接口。该结构中的at_open(),对应于用户应用层的open()接口函数,其通过mknod 创建的设备节点对设备文件进行打开操作。而对应用户层release () 接口函数的at_release () 则负责设备文件的释放操作。file_operations 中的at_ioctl()则主要是为用户提供一些控制该AT24C08 的命令。对一块具体设备进行读写操作是编写驱动要达到目的,file_operations结构体中所指向的读写函数at_read(),at_write()完成了对AT24C08 的写入和读出操作。

就写函数而言在写数据之前必须先输入测试单元的起始地址然后再对写入的数据分配相应内存然后使用copy_from_user 命令把从用户空间获得的数据拷贝到内核空间,并构造I2C 消息数据,最终通过i2c-core.c i2c-transfer()函数进行I2C消息数组的传输,i2c_transfer()将指向总线驱动中的算法i2c_algorithm 所对应的具体适配器的master_xfer()方法,这样就借助i2c-core.c 作为纽带连接了设备驱动和总线驱动,并完成了两者之间的通信,其运行流程如图的内核空间所示。

对于读函数at_read(),同样要对数据进行内存的分配,构造I2C消息,传输I2C 消息以及转换数据空间等。两者的主要区别则体现在对I2C 消息的构造上,在读出数据之前,先要写地址,根据写入的地址来寻找将要读出的数据的起始地址所以在读函数中就需要构造两条I2C 消息,一条用于写地址操作,另一条用于读数据操作。另外在转换数据空间时读函数将使用copy_to_user 把内核空间的数据拷贝到用户空间。

3.4 AT24C08 的单设备多驱动的实现方式

单设备多驱动是本文的一个创新点。设计中实现了分个设备驱动一对AT24C08 进行操作。设备驱动AT24C08的第个块操作,设备驱动对第个块操作,设备驱动对第和第个块进行操作。对块的分开操作体现在对设备地址的探测上,由于保存设备地址信息的是二元数组addr_data,所以在多驱动对单一的AT24C08 操作时就需要在该二元数组中指明每个设备驱动程序所控制的设备地址。对于控制第个块的设备驱动1,通过数组normal_addr 指出要进行操作的设备地址为0x50,如下所示:

static unsigned short normal_addr[]={ 0x50,I2C_CLIENT_END};

再通过其对数组addr_data 进行初始化这样设备驱动1就能检测到数组中所指出的AT24C08 的第个块,而跳过其他的块达到了只对单一特定块操作的目的。对于设备驱动来说只需把数组normal_addr 中地址改为AT24C08 的第个块的地址0x51 即可。同理,对设备驱动3,只需把normal_addr 中的单一地址改为两个地址即可,如下所示:

static unsigned short normal_addr [] = { 0x52,0x53, I2C_CLIENT_END};

这样就可使设备驱动只探测到后两个块,而跳过其他块,以达到对单一AT24C08 中多个块操作的目的。然后再用insmod命令加载编译好的三个.ko 驱动模块获得个不同的设备号后,接着根据所获得的设备号使用mknod 命令创建个不同的字符型设备节点最后通过用户层的个测试程序分别打开已创建的这个不同的设备节点就能分别对不同的块进行读写操作,至此就实现了单设备多驱动的控制方式。

同样除了分个驱动外驱动开发者也可以编写个设备驱动分别对每个块进行操作或者就只编写个设备驱动对个块一起操作,也适用于绑定非连续块进行操作,比如用一个设备驱动控制第和第个块。总之驱动开发人员可以根据不同的需要进行不同的组合方式。

3.5 AT24C08 设备驱动程序的验证与测试

设备驱动程序的验证需要通过用户层的测试程序来实现,测试程序如下:

fd=open("/dev/at", O_RDWR); //打开设备文件,获得设备文件的文件描述符。

scanf("%u", &start_address); //输入测试单元起始地址。

write(fd,buf,sizeof(buf)); //把以页写入方式把输入的16 个数据写入内核空间。

 

原创粉丝点击