桂宇琛李彦梅刘昊天郭 玉
(1.安庆师范大学物理与电气工程学院;2.安庆师范大学数学与计算科学学院 安徽安庆 246133)
随着科技的发展,嵌入式产品越来越丰富,从小孩的玩具,到航空科技,随处可见嵌入式设备。而对于嵌入式系统,目前世界上使用广泛的就是linux系统[1]。由于其自由,开源,Linux是使用用户最多的操作系统,它也是一个强大的多用户、多任务操作系统,支持多种处理器架构,具有可靠的系统安和良好的可移植性,所以成为了嵌入式开发系统的首选。
一个嵌入式产品分为硬件和软件两个部分[2],而其主要架构如下图 1所示:应用层主要是软件,硬件层是外接的器件。在下图中设备驱动层是具体硬件相关的实现,也是驱动开发中主要完成的部分。输入核心层主要提供一些API供设备驱动层调用,通过这些API设备驱动层上报的数据就可以传递到事件处理层。事件处理层负责创建设备文件以及将上报的事件传递到用户空间。
图1 嵌入式产品的架构图
对于一个嵌入式产品,硬件也是必不可少的部分,操作系统为了去驱动硬件,就需要将驱动硬件的方法融入内核中[3]。为了达到融合,设备驱动中就需要设计面向操作系统的接口。不同的操作系统,接口的格式就不同,这些接口由操作系统规定。本文使用Linux3.14内核的操作系统,针对i2C总线驱动编写,做了一个详细的介绍。
i2C通信协议相对于SPI和UART通信方式,有这占用接口少,速度快的特点。SPI通信需要四条线才能实现和CPU芯片通信,也就是占用了CPU芯片四个引脚。而UART通信方式虽然也只占两条线,但是他没有时序作为参考,通信的速度不能太快,否则会增加出错的概率,所以为了兼顾速度和所占用的资源,i2C通信方式无疑是最好的选择。所以i2C通信在嵌入式中应用广泛,很多传感器都会选择i2C通信协议实现和CPU芯片实现数据交互。比如我们使用的MPU6050和温度传感器ML75等。
操作系统为了使用这些传感器就需要编写驱动程序[4]。考虑到传感器设备是基于i2C总线而写的,而不是直接使用platform总线,所以需要i2C控制器驱动和传感器设备驱动同时工作。传感器的驱动基本都是一样,但是CPU芯片由于生产的公司不同,设计的i2C控制器就不同,这就导致了一个问题。每使用一个公司的芯片,就需要去编写一次驱动,而很多工作重复性很高,为了提高效率,也就是考虑到移植问题,提出了一种i2C子系统的编写模式,将i2C控制器驱动和从设备驱动分离,以提高驱动的移植性。下图2描述了i2C子系统的设计框架和原理。
图2 i2C子系统的设计框架和原理
从上图可以看出将i2C控制器驱动器和从设备驱动分离,而linux内核为了使i2C控制器驱动和从设备驱动能够更好的匹配,设定了一个标准,就是在I2C控制器和从设备驱动之间加入了一个标准的函数接口层i2c_core.c。i2C控制器由芯片生产商提供,不同的厂家可以不同,但是必须要提供统一的接口给从设备调用,这样就规范了i2C子系统的驱动编写,给工程师的使用带来了便利[5]。从上面的框架图中可以看出,linux不仅规范了i2C控制器驱动的编写,在linux内核中i2c-dev.c已经实现实现好了通用的IIC从设备驱动,所以我们编写的驱动可以使用linux提供的通用的i2C控制器驱动,我们也可以自定义去编写。下面就详细的阐述从这两个方面去编写i2C子系统驱动。
i2C通用驱动会为SOC芯片上的每一个i2C控制器生成一个设备号为89的设备节点,用户空间可以通过i2c设备节点,访问i2c控制器。每个i2c控制器的编号从0开始,对应i2c设备文件的次设备号[6]。使用通用的i2C控制器驱动,首先就要配置linux内核,linux3.14版本通用的驱动在文件i2c-dev.c,我们要将该文件编译进内核。i2c-dev.c实现的从设备驱动中,并不包括实际的从设备操作方法。它只是在提供给应用层一个设备文件,通过这个设备文件就可以找到特定的i2c控制器设备,。而i2c-dev.c的通用从设备驱动操作i2c从设备分为下面三个步骤:
1.首先确定从设备是由哪一个i2c控制器控制。
2.通过i2c控制器的设备文件,找到i2c控制器。
3.我么将i2c从设备的信息,发送给i2c控制器,然后i2c控制器收到从设备的信息后,解析从设备的信息,从而发出i2c总线时序,这样就可以和i2c从设备通信。
而在应用层对应硬件信息的描述,linux定义了一个特定的结构体i2c_msg,用于描述i2c从设备的硬件信息,然后调用ioctrl函数将找个结构体传递到i2c控制器,控制器会进行解析,然后做出相应的应答。
从上面的分析可知,linux提供的通用I2C控制器驱动,为了达到通用的效果,linux内核提供的i2C控制器驱动就没有具体的硬件信息,仅仅是封装了一些函数通用的函数,如open,read,write,ioctl。应用层的工程师需要在应用层填写从设备的硬件信息,然后通过函数去调用底层驱动接口。虽然这样做很便捷,但是给应用层的工程师带来了开发的难度,他们不仅要了解应用层的开发知识,还需要知道硬件的知识,去了解电路图和芯片手册。
自定义从设备驱动,就是将从设备的驱动封装在底层,然后提供一些应用工程师熟悉的函数接口,应用层不需要管是什么从设备,不用关心i2C总线上接的什么传感器,他们只需要调用相应的函数就可以了,这样就给应用层的开发带来了便利。
i2c从设备驱动是基于i2c总线而编写的,对于总线编写驱动的原理,我们使用“总线”“设备”“驱动”的框架去阐述[7]。arm芯片为了提高驱动的移植性,提出了AMBA总线的标准,工程师根据这个标准去编写驱动,而不是根据自己去定义,这些总线都是标准的,所有的SOC芯片都是一样的。外围设备按照速度的不同挂载在SOC内部的不同速度总线上。如表1所示列出了部分总线:
表1 总线分类表
同样为了提高移植性,驱动和设备分离,驱动中不包含硬件信息,而在设备描述硬件信息。总线会维护两条链表,分别管理设备和驱动,当一个设备被注册到总线上的时候,总线会根据其名字搜索对应的驱动,如果找到就将设备信息导入驱动程序并执行驱动;当一个驱动被注册到平台总线的时候,总线也会搜索设备。总之,平台总线负责将设备信息和驱动代码匹配,这样就可以做到驱动和设备信息的分离[8]。注册设备的流程图和注册驱动的流程图如下图所示:
图3 注册设备的流程图
注册设备的时候,一旦注册进内核,内核就会根据设备的名字去找同名的驱动,如本文设定的设备名字为led,那么内核就会去驱动链表中找led名字的驱动,发现目标匹配成功后就会调用驱动的probe函数。这个函数的作用就是获取匹配后的硬件资源并注册字符设备。同样,在注册驱动的时候,内核也会根据这个驱动的名字led去设备链表上同名的设备。同样在匹配成功后调用probe函数。
图4 注册驱动的流程图
驱动和设备文件是通过文件进行匹配,也就是如果要想设备和驱动最终能够互相匹配,就必须将设备的名字和驱动的名字设置为一样的。若驱动提供了id_table,则那设备名和id_table进行比较。若驱动没有提供id_table,则直接使用驱动名和设备名进行匹配。
本文基于linux3.14内核,分析了i2c子系统的原理和实现框架,最后将驱动移植到硬件平台上,并且编写测试程序,成功读取了MUP6050里关于加速度的相关参数。
[1]宋宝华.Linux设备驱动开发详解[M].北京:人民邮电出版社,2010.
[2]胡祖宝,董国通.基于S3C2440的嵌入式Linux内核移植及字符设备驱动开发[J].工业控制计算机,2015,28(12).
[3]Dong Yu Zhang.Design of Touch Screen Driver Based on Linux[J].KeyEngineeringMaterials,2011,1104(467):818-822.
[4]王新勇.基于ARM的Linux驱动开发研究[D].南昌:江西理工大学,2011.
[5]XuDongChen,LingChengKong,ZhiHuaZhang,DanWang,Tao Mei.Design of USB Interface Driver for WSNs Node Tester Based on Embedded Linux[J].Applied Mechanics and Materials,2011,1069(40):266-271.
[6]Peter H.Welch,Herman W.Roebbers,Jan F.Broenink,Frederick R.M.Barnes,Carl G.Ritson,Adam T.Sampson,Gardiner S.Stiles,Brian Vinter,Arjen Klomp,Herman Roebbers,Ruud Derwig,Leon Bouwmeester.Designing a Mathematically Verified ICDeviceDriverUsingASD[M].IOSPress:2009.
[7]王岩,王子牛.嵌入式Linux设备驱动程序开发[J].贵州工业大学学报(自然科学版),2008,37(1).
[8]汪海兵.嵌入式Linxu的研究与应用[D].昆明:昆明理工大学,2002.