IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。IIC总线产生于在80年代,最初为音频和视频设备开发,如今主要在服务器管理中使用,其中包括单个组件状态的通信。例如管理员可对各个组件进行查询,以管理系统的配置或掌握组件的功能状态,如电源和系统风扇。可随时监控内存、硬盘、网络、系统温度等多个参数,增加了系统的安全性,方便了管理。
1、 IIC总线的特点
IIC总线最主要的优点是其简单性和有效性。由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。IIC总线的另一个优点是,它支持多主控(multimastering), 其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。
2、IIC总线工作原理
a -- 总线构成
IIC总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,最高传送速率100kbps。各种被控制电路均并联在这条总线上,但就像电话机一样只有拨通各自的号码才能工作,所以每个电路和模块都有唯一的地址,在信息的传输过程中,IIC总线上并接的每一模块电路既是主控器(或被控器),又是发送器(或接收器),这取决于它所要完成的功能。
CPU发出的控制信号分为地址码和控制量两部分:
1) 地址码用来选址,即接通需要控制的电路,确定控制的种类;
2) 控制量决定该调整的类别(如对比度、亮度等)及需要调整的量。
这样,各控制电路虽然挂在同一条总线上,却彼此独立,互不相关。
b -- 信号类型
IIC总线在传送数据过程中共有四种类型信号:
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据;
结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据;
数据传输信号:在开始条件以后,时钟信号SCL的高电平周期期问,当数据线稳定时,数据线SDA的状态表示数据有效,即数据可以被读走,开始进行读操作。在时钟信号SCL的低电平周期期间,数据线上数据才允许改变。每位数据需要一个时钟脉冲。
应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
目前有很多半导体集成电路上都集成了IIC接口。带有IIC接口的单片机有:CYGNAL的 C8051F0XX系列,PHILIPSP87LPC7XX系列,MICROCHIP的PIC16C6XX系列等。很多外围器件如存储器、监控芯片等也提供IIC接口。
3、总线基本操作
IIC规程运用主/从双向通讯。器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。 总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。SDA线上的数据状态仅在SCL为低电平的期间才能改变,SCL为高电平的期间,SDA状态的改变被用来表示起始和停止条件。
a -- 控制字节
在起始条件之后,必须是器件的控制字节,其中高四位为器件类型识别符(不同的芯片类型有不同的定义,EEPROM一般应为1010),接着三位为片选,最后一位为读写位,当为1时为读操作,为0时为写操作。
b -- 写操作
写操作分为字节写和页面写两种操作,对于页面写根据芯片的一次装载的字节不同有所不同。关于页面写的地址、应答和数据传送的时序。
c -- 读操作
读操作有三种基本操作:当前地址读、随机读和顺序读。图4给出的是顺序读的时序图。应当注意的是:最后一个读操作的第9个时钟周期不是“不关心”。为了结束读操作,主机必须在第9个周期间发出停止条件或者在第9个时钟周期内保持SDA为高电平、然后发出停止条件。
d -- 总线仲裁
主机只能在总线空闲的时候启动传输。两个或多个主机可能在起始条件的最小持续内产生一个起始条件,结果在总线上产生一个规定的起始条件。
当SCL线是高电平时,仲裁在SDA线发生:这样,在其他主机发送低电平时,发送高电平的主机将断开它的数据输出级,因为总线上的电平和它自己的电平不同。
仲裁可以持续多位。从地址位开始,同一个器件的话接着就是数据位(如果主机-发送器),或者比较相应位(如果主机-接收器)。IIC总线的地址和数据信息由赢得仲裁的主机决定,在这个过程中不会丢失信息。
仲裁不能在下面情况之间进行:
1)重复起始条件和数据位;
2)停止条件和数据位;
3)重复起始条件和停止条件。
4、特性总结
IIC肯定是2线的(不算地线)IIC协议确实很科学,比3/4线的SPI要好,当然线多通讯速率相对就快了
IIC的原则是
a -- 在SCL=1(高电平)时,SDA千万别忽悠!!!否则,SDA下跳则"判罚"为"起始信号S",SDA上跳则"判罚"为"停止信号P".
b -- 在SCL=0(低电平)时,SDA随便忽悠!!!(可别忽悠过火到SCL跳高)
c -- 每个字节后应该由对方回送一个应答信号ACK做为对方在线的标志.非应答信号一般在所有字节的最后一个字节后.一般要由双方协议签定.
d -- SCL必须由主机发送,否则天下大乱
e -- 首字节是"片选信号",即7位从机地址加1位方向(读写)控制.从机收到(听到)自己的地址才能发送应答信号(必须应答!!!)表示自己在线.其他地址的从机不允许忽悠!!!(当然群呼可以忽悠但只能听不许说话)
f -- 读写是站在主机的立场上定义的."读"是主机接收从机数据,"写"是主机发送数据给从机.
g-- 重复位主要用于主机从发送模式到接收模式的转换"信号",由于只有2线,所以收发转换肯定要比SPI复杂,因为SPI可用不同的边沿来收发数据,而IIC不行.
h -- 在硬件IIC模块,特别是MCU/ARM/DSP等每个阶段都会得到一个准确的状态码,根据这个状态码可以很容易知道现在在什么状态和什么出错信息.
i -- 7位IIC总线可以挂接127个不同地址的IIC设备,0号"设备"作为群呼地址.10位IIC总线可以挂接更多的10位IIC设备.
二、 Linux下IIC驱动架构
Linux定义了系统的IIC驱动体系结构,在Linux系统中,IIC驱动由3部分组成,即
IIC核心
IIC总线驱动
IIC设备驱动
这3部分相互协作,形成了非常通用、可适应性很强的IIC框架。
linux i2c
上图完整的描述了linux i2c驱动架构,虽然I2C硬件体系结构比较简单,但是i2c体系结构在linux中的实现却相当复杂。
那么我们如何编写特定i2c接口器件的驱动程序?就是说上述架构中的那些部分需要我们完成,而哪些是linux内核已经完善的或者是芯片提供商已经提供的?
1、架构层次分类
第一层:提供i2c adapter的硬件驱动,探测、初始化i2c adapter(如申请i2c的io地址和中断号),驱动soc控制的i2c adapter在硬件上产生信号(start、stop、ack)以及处理i2c中断。覆盖图中的硬件实现层
第二层:提供i2c adapter的algorithm,用具体适配器的xxx_xferf()函数来填充i2c_algorithm的master_xfer函数指针,并把赋值后的i2c_algorithm再赋值给i2c_adapter的algo指针。覆盖图中的访问抽象层、i2c核心层
第三层:实现i2c设备驱动中的i2c_driver接口,用具体的i2c device设备的attach_adapter()、detach_adapter()方法赋值给i2c_driver的成员函数指针。实现设备device与总线(或者叫adapter)的挂接。覆盖图中的driver驱动层
第四层:实现i2c设备所对应的具体device的驱动,i2c_driver只是实现设备与总线的挂接,而挂接在总线上的设备则是千差万别的,所以要实现具体设备device的write()、read()、ioctl()等方法,赋值给file_operations,然后注册字符设备(多数是字符设备)。覆盖图中的driver驱动层
第一层和第二层又叫i2c总线驱动(bus),第三第四属于i2c设备驱动(device driver)。
在linux驱动架构中,几乎不需要驱动开发人员再添加bus,因为linux内核几乎集成所有总线bus,如usb、pci、i2c等等。并且总线bus中的(与特定硬件相关的代码)已由芯片提供商编写完成,例如三星的s3c-2440平台i2c总线bus为/drivers/i2c/buses/i2c-s3c2410.c
第三第四层与特定device相干的就需要驱动工程师来实现了。
2、Linux下I2C驱动体系结构三部分详细分析
a -- IIC核心
IIC 核心提供了IIC总线驱动和设备驱动的注册、注销方法,IIC通信方法(即“algorithm”,笔者认为直译为“运算方法”并不合适,为免引起误解, 下文将直接使用“algorithm”)上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
在我们的Linux驱动的i2c文件夹下有algos,busses,chips三个文件夹,另外还有i2c-core.c和i2c-dev.c两个文件。
i2c-core.c文件实现了I2C core框架,是Linux内核用来维护和管理的I2C的核心部分,其中维护了两个静态的List,分别记录系统中的I2C driver结构和I2C adapter结构。I2C core提供接口函数,允许一个I2C adatper,I2C driver和I2C client初始化时在I2C core中进行注册,以及退出时进行注销。同时还提供了I2C总线读写访问的一般接口,主要应用在I2C设备驱动中。
b -- IIC总线驱动
IIC总线驱动是对IIC硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至直接集成在CPU内部。总线驱动的职责,是为系统中每个I2C总线增加相应的读写方法。但是总线驱动本身并不会进行任何的通讯,它只是存在那里,等待设备驱动调用其函数。
IIC总线驱动主要包含了IIC适配器数据结构i2c_adapter、IIC适配器的algorithm数据结构i2c_algorithm和控制IIC适配器产生通信信号的函数。经由IIC总线驱动的代码,我们可以控制IIC适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。
Busses文件夹下的i2c-mpc.c文件实现了PowerPC下I2C总线适配器驱动,定义描述了具体的I2C总线适配器的i2c_adapter数据结构,实现比较底层的对I2C总线访问的具体方法。I2Cadapter 构造一个对I2Ccore层接口的数据结构,并通过接口函数向I2Ccore注册一个控制器。I2Cadapter主要实现对I2C总线访问的算法,iic_xfer() 函数就是I2Cadapter底层对I2C总线读写方法的实现。同时I2Cadpter 中还实现了对I2C控制器中断的处理函数。
c -- IIC设备驱动
IIC设备驱动是对IIC硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的IIC适配器上,通过IIC适配器与CPU交换数据。设备驱动则是与挂在I2C总线上的具体的设备通讯的驱动。通过I2C总线驱动提供的函数,设备驱动可以忽略不同总线控制器的差异,不考虑其实现细节地与硬件设备通讯。
IIC设备驱动主要包含了数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。
i2c-dev.c文件中实现了I2Cdriver,提供了一个通用的I2C设备的驱动程序,实现了字符类型设备的访问接口,实现了对用户应用层的接口,提供用户程序访问I2C设备的接口,包括实现open,release,read,write以及最重要的ioctl等标准文件操作的接口函数。我们可以通过open函数打开 I2C的设备文件,通过ioctl函数设定要访问从设备的地址,然后就可以通过 read和write函数完成对I2C设备的读写操作。
通过I2Cdriver提供的通用方法可以访问任何一个I2C的设备,但是其中实现的read,write及ioctl等功能完全是基于一般设备的实现,所有的操作数据都是基于字节流,没有明确的格式和意义。为了更方便和有效地使用I2C设备,我们可以为一个具体的I2C设备开发特定的I2C设备驱动程序,在驱动中完成对特定的数据格式的解释以及实现一些专用的功能。
3、重要的结构体
因为IIC设备种类太多,如果每一个IIC设备写一个驱动程序,那么显得内核非常大。不符合软件工程代码复用,所以对其层次话:
这里简单的将IIC设备驱动分为设备层、总线层。理解这两个层次的重点是理解4个数据结构,这4个数据结构是
i2c_driver
i2c_client
i2c_algorithm
i2c_adapter
i2c_driver、i2c_client属于设备层;i2c_algorithm、i2c_adapter属于总线型。如下图:
设备层关系到实际的IIC设备,总线层包括CPU中的IIC总线控制器和控制总线通信的方法。值得注意的是:一个系统中可能有很多个总线层,也就是包含多个总线控制器;也可能有多个设备层,包含不同的IIC设备。
iic-struct
由IIC总线规范可知,IIC总线由两条物理线路组成,这两条物理线路是SDA和SCL。只要连接到SDA和SCL总线上的设备都可以叫做IIC设备。
a -- i2c_client
一个IIC设备由i2c_client数据结构进行描述:
1 2 3 4 5 6 7 8 9 10 11 12 13 | struct i2c_client { unsigned short flags; //标志位 unsigned short addr; //设备的地址,低7位为芯片地址 char name[I2C_NAME_SIZE]; //设备的名称,最大为20个字节 struct i2c_adapter *adapter; //依附的适配器i2c_adapter,适配器指明所属的总线 struct i2c_driver *driver; //指向设备对应的驱动程序 struct device dev; //设备结构体 int irq; //设备申请的中断号 struct list_head list; //连接到总线上的所有设备 struct list_head detected; //已经被发现的设备链表 struct completion released; //是否已经释放的完成量 }; |
设备结构体i2c_client中addr的低8位表示设备地址。设备地址由读写位、器件类型和自定义地址组成,如下图:
iic-addr
第7位是R/W位,0表示写,2表示读,所以I2C设备通常有两个地址,即读地址和写地址;
类型器件由中间4位组成,这是由半导体公司生产的时候就已经固化了;
自定义类型由低3位组成。由用户自己设置;
IIC设备还有一些重要的注意事项:
1、i2c_client数据结构是描述IIC设备的“模板”,驱动程序的设备结构中应包含该结构;
2、adapter指向设备连接的总线适配器,系统可能有多个总线适配器。内核中静态指针数组adapters记录所有已经注册的总线适配器设备;
3、driver是指向设备驱动程序,这个驱动程序是在系统检测到设备存在时赋值的;
b -- IIC设备驱动 i2c_driver
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | struct i2c_driver { int id; //驱动标识ID unsigned int class; //驱动的类型 int (*attach_adapter)(struct i2c_adapter *); //当检测到适配器时调用的函数 int (*detach_adapter)(struct i2c_adapter*); //卸载适配器时调用的函数 int (*detach_client)(struct i2c_client *) __deprecated; //卸载设备时调用的函数 //以下是一种新类型驱动需要的函数,这些函数支持IIC设备动态插入和拔出。如果不想支持只实现上面3个。要不实现上面3个。要么实现下面5个。不能同时定义 int (*probe)(struct i2c_client *,const struct i2c_device_id *); //新类型设备探测函数 int (*remove)(struct i2c_client *); //新类型设备的移除函数 void (*shutdown)(struct i2c_client *); //关闭IIC设备 int (*suspend)(struct i2c_client *,pm_messge_t mesg); //挂起IIC设备 int (*resume)(struct i2c_client *); //恢复IIC设备 int (*command)(struct i2c_client *client,unsigned int cmd,void *arg); //使用命令使设备完成特殊的功能。类似ioctl()函数 struct devcie_driver driver; //设备驱动结构体 const struct i2c_device_id *id_table; //设备ID表 int (*detect)(struct i2c_client *,int kind,struct i2c_board_info *); //自动探测设备的回调函数 const struct i2c_client_address_data *address_data; //设备所在的地址范围 struct list_head clients; //指向驱动支持的设备 }; |
结构体i2c_driver和i2c_client的关系较为简单,其中i2c_driver表示一个IIC设备驱动,i2c_client表示一个IIC设备。关系如下图:
iic-drv-clt
c -- i2c_adapter
IIC总线适配器就是一个IIC总线控制器,在物理上连接若干个IIC设备。IIC总线适配器本质上是一个物理设备,其主要功能是完成IIC总线控制器相关的数据通信:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | struct i2c_adapter { struct module *owner; //模块计数 unsigned int id; //alogorithm的类型,定义于i2c_id.h中 unsigned int class; //允许探测的驱动类型 const struct i2c_algorithm *algo; //指向适配器的驱动程序 void *algo_data; //指向适配器的私有数据,根据不同的情况使用方法不同 int (*client_register)(struct i2c_client *); //设备client注册时调用 int (*client_unregister(struct i2c_client *); //设备client注销时调用 u8 level; struct mutex bus_lock; //对总线进行操作时,将获得总线锁 struct mutex clist_lock ; //链表操作的互斥锁 int timeout; //超时 int retries; //重试次数 struct device dev; //指向 适配器的设备结构体 int nr ; struct list_head clients; //连接总线上的设备的链表 char name[48]; //适配器名称 struct completion dev_released; //用于同步的完成量 }; |
d -- i2c_algorithm
每一个适配器对应一个驱动程序,该驱动程序描述了适配器与设备之间的通信方法:
1 2 3 4 5 6 7 | struct i2c_algorithm { int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msg, int num); //传输函数指针,指向实现IIC总线通信协议的函数,用来确定适配器支持那些传输类型 int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data); //smbus方式传输函数指针,指向实现SMBus总线通信协议的函数。SMBus和IIC之间可以通过软件方式兼容,所以这里提供了一个函数,但是一般都赋值为NULL u32 (*functionality)(struct i2c_adapter *); //返回适配器支持的功能 }; |
IIC设备驱动程序大致可以分为设备层和总线层。设备层包括一个重要的数据结构,i2c_client。总线层包括两个重要的数据结构,分别是i2c_adapter和i2c_algorithm。一个i2c_algorithm结构表示适配器对应的传输数据方法。3个数据结构关系:
iic-dev-bus
IIC设备层次结构较为简单,但是写IIC设备驱动程序却相当复杂。
IIC设备驱动程序的步骤:
iic-dev-drv
4、各结构体的作用与它们之间的关系
a -- i2c_adapter与i2c_algorithm
i2c_adapter对应与物理上的一个适配器,而i2c_algorithm对应一套通信方法,一个i2c适配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。
i2c_algorithm中的关键函数master_xfer()用于产生i2c访问周期需要的start stop ack信号,以i2c_msg(即i2c消息)为单位发送和接收通信数据。
i2c_msg也非常关键,调用驱动中的发送接收函数需要填充该结构体.
1 2 3 4 5 6 | struct i2c_msg { __u16 addr; /* slave address */ __u16 flags; __u16 len; /* msg length */ __u8 *buf; /* pointer to msg data */ }; |
b --i2c_driver和i2c_client
i2c_driver对应一套驱动方法,其主要函数是attach_adapter()和detach_client()
i2c_client对应真实的i2c物理设备device,每个i2c设备都需要一个i2c_client来描述
i2c_driver与i2c_client的关系是一对多。一个i2c_driver上可以支持多个同等类型的i2c_client.
c -- i2c_adapter和i2c_client
i2c_adapter和i2c_client的关系与i2c硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。
从i2c驱动架构图中可以看出,linux内核对i2c架构抽象了一个叫核心层core的中间件,它分离了设备驱动device driver和硬件控制的实现细节(如操作i2c的寄存器),core层不但为上面的设备驱动提供封装后的内核注册函数,而且还为下面的硬件事件提供注册接口(也就是i2c总线注册接口),可以说core层起到了承上启下的作用。
下面进入我们的驱动开发过程:
-------------------------------开发环境-----------------------------
开发板:Exynos4412
Linux 内核版本:Linux 3.14
IIC 从机对象:陀螺仪MPU6050
-------------------------------开发环境-----------------------------
首先看一张代码层次图,有助于我们的理解:
iic-struct
上面这些代码的展示是告诉我们:linux内核和芯片提供商为我们的的驱动程序提供了 i2c驱动的框架,以及框架底层与硬件相关的代码的实现。
剩下的就是针对挂载在i2c两线上的i2c设备了device,而编写的即具体设备驱动了,这里的设备就是硬件接口外挂载的设备,而非硬件接口本身(soc硬件接口本身的驱动可以理解为总线驱动)
一、编写驱动需要完成的工作
编写具体的I2C驱动时,工程师需要处理的主要工作如下:
1)提供I2C适配器的硬件驱动,探测,初始化I2C适配器(如申请I2C的I/O地址和中断号),驱动CPU控制的I2C适配器从硬件上产生。
2)提供I2C控制的algorithm, 用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋给i2c_adapter的algo指针。
3)实现I2C设备驱动中的i2c_driver接口,用具体yyy的yyy_probe(),yyy_remove(),yyy_suspend(),yyy_resume()函数指针和i2c_device_id设备ID表赋给i2c_driver的probe,remove,suspend,resume和id_table指针。
4)实现I2C设备所对应类型的具体驱动,i2c_driver只是实现设备与总线的挂接。
上面的工作中前两个属于I2C总线驱动,后面两个属于I2C设备驱动。
二、开发实例
1、查看原理图
iic-hard
对应核心板引脚:
iic-pin
从机地址
iic-address
可以获取的信息:
1、MPU6050 对应 IIC 通道5;
2、对应中断 EINT27 父节点 GPX3 3
3、因为ad0接地,所以从设备地址0x68
base address 0x138B0000
2、创建设备树节点
通过上面获取的信息,可以写出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | i2c@138b0000 { #address-cells = <1>; #size-cells = <0>; samsung,i2c-sda-delay = <100>; samsung,i2c-max-bus-freq = <20000>; pinctrl-0 = <&i2c5_bus>; pinctrl-names = "default"; status = "okay"; pmu6050-3-asix@68 { compatible = "invense,mpu6050"; reg = <0x68>; interrupt-parent = <&gpx3>; interrupts = <3 2>; }; }; |
3、MPU6050相应寄存器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #define SMPLRT_DIV 0x19 //采样率分频,典型值: 0x07(125Hz) */ #define CONFIG 0x1A // 低通滤波频率,典型值: 0x06(5Hz) */ #define GYRO_CONFIG 0x1B // 陀螺仪自检及测量范围,典型值: 0x18(不自检,2000deg/s) */ #define ACCEL_CONFIG 0x1C // 加速计自检、测量范围及高通滤波频率,典型值: 0x01(不自检, 2G, 5Hz) */ #define ACCEL_XOUT_H 0x3B // 存储最近的 X 轴、 Y 轴、 Z 轴加速度感应器的测量值 */ #define ACCEL_XOUT_L 0x3C #define ACCEL_YOUT_H 0x3D #define ACCEL_YOUT_L 0x3E #define ACCEL_ZOUT_H 0x3F #define ACCEL_ZOUT_L 0x40 #define TEMP_OUT_H 0x41 // 存储的最近温度传感器的测量值 */ #define TEMP_OUT_L 0x42 #define GYRO_XOUT_H 0x43 // 存储最近的 X 轴、 Y 轴、 Z 轴陀螺仪感应器的测量值 */ #define GYRO_XOUT_L 0x44 #define GYRO_YOUT_H 0x45 #define GYRO_YOUT_L 0x46 #define GYRO_ZOUT_H 0x47 #define GYRO_ZOUT_L 0x48 #define PWR_MGMT_1 0x6B // 电源管理,典型值: 0x00(正常启用) */ #define WHO_AM_I 0x75 //IIC 地址寄存器(默认数值 0x68,只读) */ |
4、具体程序
mpu6050.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #ifndef MPU6050_HHHH #define MPU6050_HHHH #define MPU6050_MAGIC 'K' union mpu6050_data { struct { unsigned short x; unsigned short y; unsigned short z; }accel; struct { unsigned short x; unsigned short y; unsigned short z; }gyro; unsigned short temp; }; #define GET_ACCEL _IOR(MPU6050_MAGIC, 0, union mpu6050_data) #define GET_GYRO _IOR(MPU6050_MAGIC, 1, union mpu6050_data) #define GET_TEMP _IOR(MPU6050_MAGIC, 2, union mpu6050_data) #endif |
i2c_driver
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | #include <linux/kernel.h> #include <linux/module.h> #include <linux/i2c.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/fs.h> #include <linux/delay.h> #include <asm/uaccess.h> #include "mpu6050.h" MODULE_LICENSE("GPL"); #define SMPLRT_DIV 0x19 #define CONFIG 0x1A #define GYRO_CONFIG 0x1B #define ACCEL_CONFIG 0x1C #define ACCEL_XOUT_H 0x3B #define ACCEL_XOUT_L 0x3C #define ACCEL_YOUT_H 0x3D #define ACCEL_YOUT_L 0x3E #define ACCEL_ZOUT_H 0x3F #define ACCEL_ZOUT_L 0x40 #define TEMP_OUT_H 0x41 #define TEMP_OUT_L 0x42 #define GYRO_XOUT_H 0x43 #define GYRO_XOUT_L 0x44 #define GYRO_YOUT_H 0x45 #define GYRO_YOUT_L 0x46 #define GYRO_ZOUT_H 0x47 #define GYRO_ZOUT_L 0x48 #define PWR_MGMT_1 0x6B #define MPU6050_MAJOR 500 #define MPU6050_MINOR 0 struct mpu6050_device { struct cdev cdev; struct i2c_client *client; }; struct mpu6050_device *mpu6050; static int mpu6050_read_byte(struct i2c_client *client, unsigned char reg) { int ret; char txbuf[1] = { reg }; char rxbuf[1]; struct i2c_msg msg[2] = { {client->addr, 0, 1, txbuf}, {client->addr, I2C_M_RD, 1, rxbuf} }; ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); if (ret < 0) { printk("ret = %d\n", ret); return ret; } return rxbuf[0]; } static int mpu6050_write_byte(struct i2c_client *client, unsigned char reg, unsigned char val) { char txbuf[2] = {reg, val}; struct i2c_msg msg[2] = { {client->addr, 0, 2, txbuf}, }; i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); return 0; } static int mpu6050_open(struct inode *inode, struct file *file) { return 0; } static int mpu6050_release(struct inode *inode, struct file *file) { return 0; } static long mpu6050_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { union mpu6050_data data; struct i2c_client *client = mpu6050->client; switch(cmd) { case GET_ACCEL: data.accel.x = mpu6050_read_byte(client, ACCEL_XOUT_L); data.accel.x |= mpu6050_read_byte(client, ACCEL_XOUT_H) << 8; data.accel.y = mpu6050_read_byte(client, ACCEL_YOUT_L); data.accel.y |= mpu6050_read_byte(client, ACCEL_YOUT_H) << 8; data.accel.z = mpu6050_read_byte(client, ACCEL_ZOUT_L); data.accel.z |= mpu6050_read_byte(client, ACCEL_ZOUT_H) << 8; break; case GET_GYRO: data.gyro.x = mpu6050_read_byte(client, GYRO_XOUT_L); data.gyro.x |= mpu6050_read_byte(client, GYRO_XOUT_H) << 8; data.gyro.y = mpu6050_read_byte(client, GYRO_YOUT_L); data.gyro.y |= mpu6050_read_byte(client, GYRO_YOUT_H) << 8; data.gyro.z = mpu6050_read_byte(client, GYRO_ZOUT_L); data.gyro.z |= mpu6050_read_byte(client, GYRO_ZOUT_H) << 8; break; case GET_TEMP: data.temp = mpu6050_read_byte(client, TEMP_OUT_L); data.temp |= mpu6050_read_byte(client, TEMP_OUT_H) << 8; break; default: printk("invalid argument\n"); return -EINVAL; } if (copy_to_user((void *)arg, &data, sizeof(data))) return -EFAULT; return sizeof(data); } struct file_operations mpu6050_fops = { .owner = THIS_MODULE, .open = mpu6050_open, .release = mpu6050_release, .unlocked_ioctl = mpu6050_ioctl, }; static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id) { int ret; dev_t devno = MKDEV(MPU6050_MAJOR, MPU6050_MINOR); printk("match OK!\n"); mpu6050 = kzalloc(sizeof(*mpu6050), GFP_KERNEL); if (mpu6050 == NULL) { return -ENOMEM; } mpu6050->client = client; ret = register_chrdev_region(devno, 1, "mpu6050"); if (ret < 0) { printk("failed to register char device region!\n"); goto err1; } cdev_init(&mpu6050->cdev, &mpu6050_fops); mpu6050->cdev.owner = THIS_MODULE; ret = cdev_add(&mpu6050->cdev, devno, 1); if (ret < 0) { printk("failed to add device\n"); goto err2; } mpu6050_write_byte(client, PWR_MGMT_1, 0x00); mpu6050_write_byte(client, SMPLRT_DIV, 0x07); mpu6050_write_byte(client, CONFIG, 0x06); mpu6050_write_byte(client, GYRO_CONFIG, 0xF8); mpu6050_write_byte(client, ACCEL_CONFIG, 0x19); return 0; err2: unregister_chrdev_region(devno, 1); err1: kfree(mpu6050); return ret; } static int mpu6050_remove(struct i2c_client *client) { dev_t devno = MKDEV(MPU6050_MAJOR, MPU6050_MINOR); cdev_del(&mpu6050->cdev); unregister_chrdev_region(devno, 1); kfree(mpu6050); return 0; } static const struct i2c_device_id mpu6050_id[] = { { "mpu6050", 0}, {} }; static struct of_device_id mpu6050_dt_match[] = { {.compatible = "invense,mpu6050" }, {/*northing to be done*/}, }; struct i2c_driver mpu6050_driver = { .driver = { .name = "mpu6050", .owner = THIS_MODULE, .of_match_table = of_match_ptr(mpu6050_dt_match), }, .probe = mpu6050_probe, .remove = mpu6050_remove, .id_table = mpu6050_id, }; static init _init mpu6050_init(void) { return i2c_add_driver(&mpu6050_driver); } static void _exit mpu6050_exit(void) { return i2c_del_driver(&mpu6050_driver); } module_init(&mpu6050_init); module_exit(&mpu6050_exit); |
test.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include "mpu6050.h" int main(int argc, const char *argv[]) { int fd; union mpu6050_data data; fd = open("/dev/mpu6050", O_RDWR); if (fd < 0) { perror("open"); exit(1); } while(1) { ioctl(fd, GET_ACCEL, &data); printf("acceleration data: x = %04x, y = %04x, z = %04x\n", data.accel.x, data.accel.y, data.accel.z); ioctl(fd, GET_GYRO, &data); printf("gyroscope data: x = %04x, y = %04x, z = %04x\n", data.accel.x, data.accel.y, data.accel.z); sleep(1); } close(fd); return 0; } |
转自:http://blog.csdn.net/zqixiao_09/article/details/50917655