总线类设备驱动——IIC

目录

一、本章目标

二、IIC设备驱动

2.1  I2C协议简介

2.2 LinuxI2C驱动

2.3 I2C 设备驱动实例


一、本章目标

        一条总线可以将多个设备连接在一起,提高了系统的可扩展性能。这个互联的系统通常由三部分组成:总线控制器、物理总线(一组信号线) 和设备。总线控制器和设备通过总线连接在一起,总线控制器可以发起对总线上设备的访问操作。通常总线控制器有一个驱动程序,用于控制总线控制器来操作总线,从而来访问设备,这一类驱动通常在内核中都已实现了。连接在总线上的设备也可能有一段程序要运行,这段程序可能是不基于操作系统的裸机程序 (俗称固件),也可能是基于操作系统的一个驱动。最后还有一类驱动程序,它们通常调用总线控制器的驱动程序来完成对总线上具体设备的访问,这类驱动程序的叫法不一,这里统称为设备驱动(一定要和前面说到的运行在设备本身的固件或驱动区分开),这也是本章重点讨论的内容。本章依次讨论了最常见的I2C总线、SPI总线、USB总线和PCI总线的设备驱动。今天先来学习IIC。

二、IIC设备驱动


2.1  I2C协议简介


        I2C(Intel-Integrated Circuit)是由飞利浦(现在叫恩智浦)公司开发的一种慢速两线制总线协议。最初总线的速率定为 100KHz,经过发展,速率出现了 400KHz、3.4MHZ、1MHz和5MHz的不同等级,目前大多数器件都支持400KHz。不过速率是可变的,比如一个支持400KHz的器件,完全可以工作在299KHz这个速率上,只要不超过400KHz即可。I2C 总线为那些不需要经常访问、低带宽的设备提供了一种廉价的互联方式,被广泛地应用在嵌入式系统设备当中。不过 I2C 总线协议有商标上的一些限制,有些厂家为了避免这种限制,使用了另外一种总线协议 SMBus(System Management Bus)。SMBus 总线协议其实是I2C总线协议的一个子集,绝大多数I2C 设备都能够在SMBus 总线上工作现在的计算机主板通常使用 SMBus 总线,最常见的就是内存条上的 EEPROM 配置芯片.

        I2C 总线由 SCL (串行时钟线)和 SDA(串行数据线)两条线组成,其上连接有主机控制器(Master,在 Linux 驱动中称为 Adapter) 和从设备 (Slave,在 Linux 驱动中称为 Client)。所有的访问操作都是由主机控制器发起的,在同一条总线上可以有多个主机控制器,当多个主机控制器同时对设备发起访问时,由协议的冲突检测和仲裁机制来保证只有一个主机控制器对从设备进行访问。多个从设备是通过从设备的地址来区分的,地址分为7位地址和10 位地址两种,常见的是7位地址。下图是具有两个主机控制器的I2C总线连接图,总线控制器由微控制器来充当,从设备有门阵列、LCD 驱动器ADC和 EEPROM。


下面以图示方式来说明12C 的总线时序


开始位:当SCL为高电平时,SDA 由高电平变为低电平的期间,如图中最左边的STARTcondition 所标记的区域,这表示主机控制器要开始对从机发起访问了。
地址位:接下来的7个时钟周期,主机控制器将会发送从机的 7位地址(如果是 10位地址需要分两次发送),如图中ADDRESS 所标记的区域。
读/写位:在第8个时钟周期,如果 SDA 为高电平则表示接下来要读取从机的数据如果是低电平则表示主机要写数据到从机,如图 10.2中 R/W所标记的区域。
应答位:在第9个时钟周期由从机进行应答,低电平为 ACK,高电平为 NACK,如果从机响应,应该发ACK。

数据位:在接下来的若干个周期内,主机可以持续读取数据(如果读/写位为读),或号数据(如果读/写位为写),每次数据传输完成(如图 10.2中的 DATA 所标记的区域)也要进行应答,是读则由主机控制器来应答,是写则由从机来应答,只是在主机读完最后一个字节的数据后应该以 NACK 来应答。
停止位::当SCL 为高电平时,SDA 由低电平变为高电平的期间,如图10.2中最右边的STOP condition 所标记的区域,这表示主机控制器结束了对从机的访问

        I2C 从设备内部通常有若干个寄存器,每个寄存器都有一个地址,对这些从设备的访问通常是顺序访问的。比如上次从寄存器 2开始连续访问了 4 个寄存器,那么下次访问将从寄存器6开始。不过按照下图的时序,可以实现随机的读和写访问。


        随机写访问是在主机发送完从机地址和写标志位,从机应答后,主机继续写寄存器地址,如图 10.3 中的 RA 所示。当从机对写入的寄存器地址进行应答后,主机才写入真正的数据。对随机读访问则要复杂一些,在寄存器地址写入,从机应答后,主机需要再次发送开始位,然后发送从机地址和读标志位,之后才是读操作。


2.2 LinuxI2C驱动


I2C驱动层次结构如图10.5所示。


I2C主机驱动:I2C 主机控制器的驱动,一般由 SoC 芯片厂商负责设计实现,用于控制I2C主机控制器发出时序信号。
I2CCore:为上层提供统一的API接口和对其他模块进行注册和注销等管理等.

I2C 设备驱动:调用12C Core 提供的统一 API,根据I2C 设备的访问规范,控制I2主机控制器发出不同的时序信号,对12C 设备进行访问。该驱动称为内核层I2C 设备驱动

i2c-dev:将I2C主机控制器实现为一个字符设备,应用程序可以直接访问/dev/i2c-N来访问12C 主机控制器,从而对12C 设备发起访问,该应用程序称为应用层I2C 设备驱动

        I2C Core 为屏蔽不同的 I2C 主机控制器驱动提供了可能,可以使 I2C 设备驱动仅关心如何操作 I2C 设备,而不需要了解 12C 主机控制器的细节,从而使I2C 设备驱动可以独立的存在,适用于各种不同的硬件平台。
        I2C 驱动和我们之前接触到的平台总线设备驱动非常类似,都有总线、设备和驱动这三者。12C 的设备由 struct i2c_client 来进行描述,类似于 struct platform_device,但是我们较少自己来创建这个结构对象。通常,在一个嵌入式系统中,我们是知道一个 I2C设备的地址、驱动的名字和连接的主机控制器等信息的。对 I2C 设备的描述和向内核的注册操作可以通过类似于下面的代码来实现具体可以参考linux-3.14/arch/arm/mach-s3c24xx/mach-mini2440.c

486 /*
487  * I2C devices
488  */
489 static struct at24_platform_data at24c08 = {
490     .byte_len   = SZ_8K / 8,
491     .page_size  = 16,
492 };
493 
494 static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
495     {
496         I2C_BOARD_INFO("24c08", 0x50),
497         .platform_data = &at24c08,
498     },
499 };
......
676     i2c_register_board_info(0, mini2440_i2c_devs,
677                 ARRAY_SIZE(mini2440_i2c_devs));


        i2c_board_info 用于描述某些信息已知的 12C 设备,通常用I2C_BOARD_INFO宏来描述基本信息,包括驱动的名字和设备的地址。上面的代码定义了一个 i2c_board_info的数组 mini2440_i2c_devs,里面有一个设备,其对应的12C 设备驱动的名字是 24c08,设备地址为0x50。另外,platform_data 指定了其他的一些设备数据,如这片 EEPROM 的存储容量和页大小信息。最后使用 i2c_register_board_info 将 mini2440_i2c_devs 数组中的所有设备都注册到了 0号 I2C 总线上。


        但随着设备树的出现,上述方法基本被淘汰了,取而代之的是用设备树节点来进行I2C 设备的描述。Exynos4412 的 I2C 设备节点描述请参见内核文档 Documentation/devicetree/bindings/i2c/i2c-s3c2410.txt。在该文档中,给出了一个具体的实例,代码如下。

38     i2c@13870000 {                                                                                 
 39         compatible = "samsung,s3c2440-i2c";
 40         reg = <0x13870000 0x100>;
 41         interrupts = <345>;
 42         samsung,i2c-sda-delay = <100>;
 43         samsung,i2c-max-bus-freq = <100000>;
 44         /* Samsung GPIO variant begins here */
 45         gpios = <&gpd1 2 0 /* SDA */
 46              &gpd1 3 0 /* SCL */>;
 47         /* Samsung GPIO variant ends here */
 48         /* Pinctrl variant begins here */
 49         pinctrl-0 = <&i2c3_bus>;
 50         pinctrl-names = "default";
 51         /* Pinctrl variant ends here */
 52         #address-cells = <1>;
 53         #size-cells = <0>;
 54 
 55         wm8994@1a {
 56             compatible = "wlf,wm8994";
 57             reg = <0x1a>;
 58         };
 59     };


        i2c@13870000 是一个12C 主机控制器节点,里面的 compatible、reg、interrupts 等属性指定了其匹配的主机控制器驱动、I/O 内存的范围和使用的中断等信息。作为一个 12C设备驱动开发者来说,这些信息我们通常不关心。我们需要关心的是接在这个主机控制器上的 12C 设备的子节点如何来描述。上面例子中的子节点 wm8994@la 中的 compatible属性给出了匹配的驱动,而 reg 指定了 12C 设备的地址。也就是说,如果我们要编写一个I2C 设备的节点信息,则只需要在对应的 I2C 主机控制器节点中编写一个子节点,给出compatible 和reg 属性即可。对比一下,这其实和 I2c_board_info 所描述的信息基本一样只是形式不同而已。
        在内核的启动过程中,会自动将这些信息转换为一个 struct i2c_client 结构对象,当有匹配的驱动注册时,同样会像平台驱动一样,调用其 probe 函数。接下来我们就来看看I2C 驱动,核心的数据结构是 struct i2c_driver,其定义如下。
 

struct i2c_driver{
......
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);\
    int (*remove)(struct i2c_client *);
    void (*shutdown)(struct i2c_client *);
    int (*suspend)(struct i2c_client *, pm_message_t mesg);
    int (*resume)(struct i2c_client *);
......
    struct device_driver driver;
    const struct i2c_device_id *id_table;
......
};


        上面省略了 I2C 设备驱动开发者较少关心的成员,不难发现,这和前面的 struct platform_driver 基本一样,所以在此就不再赘述。struct i2c_driver 相关的主要 API 罗列如下。

i2c_add_driver(driver)

void i2c_del_driver(struct i2c_driver *driver);

void i2c_set_clientdata(struct i2c_client *dev, void *data);

void *i2c_get_clientdata(const struct i2c_client *dev);

int i2c_check_functionality(struct i2c_adapter *adap, u32 func);


i2c add driver; 添加I2C 设备驱动。
i2c del driver: 删除 I2C 设备驱动。
i2c set clientdata: 将 data 保存在 struct i2c client 结构中
i2c get_clientdata: 从 struct i2c_client 中获取保存的数据
i2c_check_functionality: 查 12C 从设备所绑定的主机控制器是否支持 func 中指定的所有功能,常见的功能如下。

I2C_FUNC_10BIT_ADDR: 十位地址

I2C_PUNC_SMBUS_READ_BYTE_DATA:单字节随机读

I2C_EUNC_SMBUS_WRITE_BYTE_DATA: 单字节随机写

I2C_FUNC_SMBUS_READ_BLOCK_DATA: 多字节随机读

I2C_FUNC_SMBUS_WRITE_BLOCK_DATA:多字节随机写


简单的 I2C 数据收发相关的函数原型如下。
int i2c_master_send(const struct i2c_client *client, const char *buf, int count);

int i2c_master_recv(const struct 12c_client *client, char *buf, int count);

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);

i2c_master_send:向12C 从设备写数据,I2C 设备的地址包含在 client 中,要写的数
缓冲区地址为 buf,写的字节数为 count,返回成功写入的字节数,负数表示失败。

i2c_master_recv: 从12C 从设备读数据,参数和返回值同写操作。

i2c_transfer: 可以将几个读写操作合并在一起执行的函数,adap 是执行读写操作的2C主机控制器,msgs 是消息数组,num 是消息的条数。消息的结构类型定义如下。

struct i2c_msg
{
    _u16 addr;
    _u16 flags;
    _ul6 len;
    _u8 *buf;
};


addr:I2C 从设备的地址。
flags: 操作的一些标志,常见的有 I2C_M_RD,表示本次是读操作。I2C_M_TEN,表示使用的是 10 位 I2C 设备地址。
len:本次要读或写的数据字节数。
buf:数据缓冲区指针。
如果要向设备先写后读,可以通过类似于下面的代码来实现。
 

msgs[0].len=1;

msgs[0].addr=0x48;

msgs[0].flags=0;

msgs[0].buf=txbuf;

msgs[0].buf[0]=0x0;
msgs[1].len=2;

msgs[1].addr=0x48;

msgs[1],flags=I2C_M_RD;

msgs[1].buf=rxbuf;

msgs[1].buf[0]=0;
msgs[1].buf[1]=0;

i2c_transfer(client->adapter, msgs,2);

        上面的代码构造了两条消息,两条消息都是要操作设备地址为 0x48 的 12C 备,第一条消息是向设备写 1个字节的数据,第二条消息是读两个字节的数据。

        i2c_transfer 不要求每条消息的地址都是一样的,该函数返回被成功执行的消息条数
为负表示失败。还有一些适合于 SMBus 的更简单的 API,如下所示。

s32 i2c_smbus_read_byte_data (struct i2c_client *client, u8 command);
s32 i2c_smbus_write_byte_data(struct i2c_client *client, u8 command, u8 value);
s32 i2c_smbus_read_block_data(struct i2c_client *client, u8 command, u8 *values);
s32 i2c_smbus_write_block_data(struct i2c_elient *client, u8 command, u8 length,const u8 *values);

        上面的函数为单字节或多字节的随机读写函数,command 是 SMBus 中的命令,通常是 i2C 设备内部的寄存器地址。返回的是实际读写的字节数,为负表示失败。其他参数都很好理解,这里不再详细说明。
        我们前面谈到,如果把 12C 主机控制器实现为一个字符设备,应用程序可以通过对应的设备来直接访问 12C 主机控制器,从而产生相应的时序来访问 12C 从设备,这样应用程序叫应用层 i2C 设备驱动。不过这需要配置内核,以确保这一功能被选中,配置如下。


内核配置并重新编译后,目标板使用新的内核镜像,系统启动后,可以看到如下的
设备。


使能的主机控制器节点越多,设备文件就会越多。
应用层也有相关的 API,最主要的如下。

ioctl(file,I2C_SLAVE,long addr)
ioctl(file,I2C FUNCS, unsigned long *funcs)
ioctl(file,I2C_RDWR, struct i2c_rdwr_ioctl_data *msgset)
__s32 i2c_smbus_read_byte_data(int file,__u8 command)
__s32 i2c_smbus_write_byte_data(int file,__u8 command,__u8 value);
__s32 i2c_smbus_read_block_data(int file,__u8 command,__u8 *values);
__s32 i2c_smbus_write_block_data(int file, __u8 command, __u8 length,__u8 *values);


I2C_SLAVE: 设置要访问的从机设备地址。
I2C_FUNCS: 获取主机控制器的功能。
I2C_RDWR:读写 12C 设备,读写操作由 msgset 来指定,其类型定义如下。

struct i2c_rdwr_ioctl_data 
{
    struct i2c_msg *msgs;
    int nmsgs;
}


        其中的 struct i2c_msg 我们在前面已经见过了。针对前面的内核层 i2C 设备驱动的例子而言,应用层 I2C 设备驱动代码如下。
 

i2c_data.nmsgs=2;
i2c_data.msgs[0].len=1;
i2c_data.msgs[0].addr=0x48;
12c_data.msgs[0].flags=0;
i2c_data.msgs[0].buf=txbuf;
i2c_data.msgs[0].buf[0]=0x0;
i2c_data.msgs[1].len=2;
i2c_data.msgs[1].addr=0x48;
i2c_data.msgs[1].flags=I2C_M_RD;
i2c_data.msgs[1].buf=rxbuf;
i2c_data.msgs[1].buf[0]=0;
i2c_data.msgs[1].buf[1]=0;
ioctl(fd,I2C_RDWR,(unsigned long)&i2c_data);


i2c_smbus_read_byte_data 之类的函数和前面内核层中的含义一样,此处不再细述.


2.3 I2C 设备驱动实例


        在 FS4412 目标板上有一个集陀螺仪、三轴加速度传感器和温度传感器于一体的器件MPU6050,相关的原理图如图所示。


        MPU6050 连接在 Exynos4412 编号为 5 的 i2C 主机控制器上,MPU6050 设备的地址为 0x68,根据前面的知识并查阅 Exynos4412 的用户手册,可以得出相应的设备节点代码。

i2c@138B0000{
    samsung,i2c-sda-delay = <100>;
    samsung,i2c-max-bus-freq = <20000>;
    pinctr1-0 = <&i2c5_bus>;
    pinctrl-names = "default";
    status = "okay";
    mpu6050@68 {
        compatible="fs4412,mpu6050";
        reg = <0x68>;
    };
};


        重新编译设备树给目标板使用,系统启动后,将会多出一个 i2c-5 的字符设备,

正常应该只有一个i2c-0设备的,上面那个是已经配好的所以有个i2c-5
MPU6050 的寄存器非常多,但是只是简单获取一些传感数据,关注如图几个寄存器就可以了。



/****************MPU6050内部常用寄存器地址****************/

#define	SMPLRT_DIV		0x19	//陀螺仪采样率,典型值:0x07(125Hz)
#define	CONFIG			0x1A	//低通滤波频率,典型值:0x06(5Hz)
#define	GYRO_CONFIG		0x1B	//陀螺仪自检及测量范围,典型值:0x18(不自检,2000°/s)
#define	ACCEL_CONFIG	0x1C	//加速计自检及测量范围及高通滤波频率,典型值:0x0(不自检,2G,5Hz)
#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	//电源管理,典型值:0x00(正常启用)
#define	SlaveAddress	0x68	//MPU6050-I2C地址

SMPLRT_DIV: 采样时钟分频值,相应的公式为:采样率 = 陀螺仪输出频率 /(1 + SMPLRT_DIV)。
CONFIG: EXT_SYNC_SET 是外部同步的配置,不需要设为0 即可。DLPF_CFG是数字低通滤波器的配置,它将决定陀螺仪和三轴加速度传感器的带宽和延时值,最大可配置的值为 6。
GYRO_CONFIG: 陀螺配置寄存器,FS_SEL 用于选择陀螺仪的输出范围,设置值和范围如图 所示


ACCEL_CONFIG: 三轴加速度传感器配置寄存器,AFS_SEL 用于三轴加速度传感器的输出范围,设置值和范围如图所示。


        ACCEL_xxx、TEMP_xxx、GYRO xxx:传感器输出值,三者的换算关系如下。

        三轴加速度 = 采样值 /n, n 根据 FS_SEL 的值 0、1、2、3 分别对应为 16384、8192、4096、2048。
        温度 = 采样值 /340 +36.53。

        陀螺仪角速度 = 采样值 /n,n 根据 AFS_SEL 的值 0、1、2、3 分别对应为 131、65.5、32.8、16.4。
        PWR_MGMT_1:电源管理寄存器 1。DEVICE_RESET 用于复位整个芯片。SLEEP是休眠控制,为1表示休眠。CYCLE 是循环模式控制,如果 SLEEP 为0,CYCLE为1,那么传感器将会定期醒来进行采样。TEMP_ DIS 为1禁止温度传感器,CLKSEL 为0选择内部的 8MHz 时钟。
        根据以上对寄存器的介绍,可以写出如下的应用层 I2C 设备驱动代码

/*
    i2c-dev.h - i2c-bus driver, char device interface

    Copyright (C) 1995-97 Simon G. Vogl
    Copyright (C) 1998-99 Frodo Looijaard <[email protected]>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
    MA 02110-1301 USA.
*/

/* $Id: i2c-dev.h 5361 2008-10-19 09:47:02Z khali $ */

#ifndef LIB_I2CDEV_H
#define LIB_I2CDEV_H

#include <linux/types.h>
#include <sys/ioctl.h>


/* -- i2c.h -- */


/*
 * I2C Message - used for pure i2c transaction, also from /dev interface
 */
struct i2c_msg {
	__u16 addr;	/* slave address			*/
	unsigned short flags;		
#define I2C_M_TEN	0x10	/* we have a ten bit chip address	*/
#define I2C_M_RD	0x01
#define I2C_M_NOSTART	0x4000
#define I2C_M_REV_DIR_ADDR	0x2000
#define I2C_M_IGNORE_NAK	0x1000
#define I2C_M_NO_RD_ACK		0x0800
	short len;		/* msg length				*/
	char *buf;		/* pointer to msg data			*/
};

/* To determine what functionality is present */

#define I2C_FUNC_I2C			0x00000001
#define I2C_FUNC_10BIT_ADDR		0x00000002
#define I2C_FUNC_PROTOCOL_MANGLING	0x00000004 /* I2C_M_{REV_DIR_ADDR,NOSTART,..} */
#define I2C_FUNC_SMBUS_PEC		0x00000008
#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL	0x00008000 /* SMBus 2.0 */
#define I2C_FUNC_SMBUS_QUICK		0x00010000 
#define I2C_FUNC_SMBUS_READ_BYTE	0x00020000 
#define I2C_FUNC_SMBUS_WRITE_BYTE	0x00040000 
#define I2C_FUNC_SMBUS_READ_BYTE_DATA	0x00080000 
#define I2C_FUNC_SMBUS_WRITE_BYTE_DATA	0x00100000 
#define I2C_FUNC_SMBUS_READ_WORD_DATA	0x00200000 
#define I2C_FUNC_SMBUS_WRITE_WORD_DATA	0x00400000 
#define I2C_FUNC_SMBUS_PROC_CALL	0x00800000 
#define I2C_FUNC_SMBUS_READ_BLOCK_DATA	0x01000000 
#define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000 
#define I2C_FUNC_SMBUS_READ_I2C_BLOCK	0x04000000 /* I2C-like block xfer  */
#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK	0x08000000 /* w/ 1-byte reg. addr. */

#define I2C_FUNC_SMBUS_BYTE (I2C_FUNC_SMBUS_READ_BYTE | \
                             I2C_FUNC_SMBUS_WRITE_BYTE)
#define I2C_FUNC_SMBUS_BYTE_DATA (I2C_FUNC_SMBUS_READ_BYTE_DATA | \
                                  I2C_FUNC_SMBUS_WRITE_BYTE_DATA)
#define I2C_FUNC_SMBUS_WORD_DATA (I2C_FUNC_SMBUS_READ_WORD_DATA | \
                                  I2C_FUNC_SMBUS_WRITE_WORD_DATA)
#define I2C_FUNC_SMBUS_BLOCK_DATA (I2C_FUNC_SMBUS_READ_BLOCK_DATA | \
                                   I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)
#define I2C_FUNC_SMBUS_I2C_BLOCK (I2C_FUNC_SMBUS_READ_I2C_BLOCK | \
                                  I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)

/* Old name, for compatibility */
#define I2C_FUNC_SMBUS_HWPEC_CALC	I2C_FUNC_SMBUS_PEC

/* 
 * Data for SMBus Messages 
 */
#define I2C_SMBUS_BLOCK_MAX	32	/* As specified in SMBus standard */	
#define I2C_SMBUS_I2C_BLOCK_MAX	32	/* Not specified but we use same structure */
union i2c_smbus_data {
	__u8 byte;
	__u16 word;
	__u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
	                                            /* and one more for PEC */
};

/* smbus_access read or write markers */
#define I2C_SMBUS_READ	1
#define I2C_SMBUS_WRITE	0

/* SMBus transaction types (size parameter in the above functions) 
   Note: these no longer correspond to the (arbitrary) PIIX4 internal codes! */
#define I2C_SMBUS_QUICK		    0
#define I2C_SMBUS_BYTE		    1
#define I2C_SMBUS_BYTE_DATA	    2 
#define I2C_SMBUS_WORD_DATA	    3
#define I2C_SMBUS_PROC_CALL	    4
#define I2C_SMBUS_BLOCK_DATA	    5
#define I2C_SMBUS_I2C_BLOCK_BROKEN  6
#define I2C_SMBUS_BLOCK_PROC_CALL   7		/* SMBus 2.0 */
#define I2C_SMBUS_I2C_BLOCK_DATA    8


/* ----- commands for the ioctl like i2c_command call:
 * note that additional calls are defined in the algorithm and hw 
 *	dependent layers - these can be listed here, or see the 
 *	corresponding header files.
 */
				/* -> bit-adapter specific ioctls	*/
#define I2C_RETRIES	0x0701	/* number of times a device address      */
				/* should be polled when not            */
                                /* acknowledging 			*/
#define I2C_TIMEOUT	0x0702	/* set timeout - call with int 		*/


/* this is for i2c-dev.c	*/
#define I2C_SLAVE	0x0703	/* Change slave address			*/
				/* Attn.: Slave address is 7 or 10 bits */
#define I2C_SLAVE_FORCE	0x0706	/* Change slave address			*/
				/* Attn.: Slave address is 7 or 10 bits */
				/* This changes the address, even if it */
				/* is already taken!			*/
#define I2C_TENBIT	0x0704	/* 0 for 7 bit addrs, != 0 for 10 bit	*/

#define I2C_FUNCS	0x0705	/* Get the adapter functionality */
#define I2C_RDWR	0x0707	/* Combined R/W transfer (one stop only)*/
#define I2C_PEC		0x0708	/* != 0 for SMBus PEC                   */

#define I2C_SMBUS	0x0720	/* SMBus-level access */

/* -- i2c.h -- */


/* Note: 10-bit addresses are NOT supported! */

/* This is the structure as used in the I2C_SMBUS ioctl call */
struct i2c_smbus_ioctl_data {
	char read_write;
	__u8 command;
	int size;
	union i2c_smbus_data *data;
};

/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
	struct i2c_msg *msgs;	/* pointers to i2c_msgs */
	int nmsgs;		/* number of i2c_msgs */
};


static inline __s32 i2c_smbus_access(int file, char read_write, __u8 command, 
                                     int size, union i2c_smbus_data *data)
{
	struct i2c_smbus_ioctl_data args;

	args.read_write = read_write;
	args.command = command;
	args.size = size;
	args.data = data;
	return ioctl(file,I2C_SMBUS,&args);
}


static inline __s32 i2c_smbus_write_quick(int file, __u8 value)
{
	return i2c_smbus_access(file,value,0,I2C_SMBUS_QUICK,NULL);
}
	
static inline __s32 i2c_smbus_read_byte(int file)
{
	union i2c_smbus_data data;
	if (i2c_smbus_access(file,I2C_SMBUS_READ,0,I2C_SMBUS_BYTE,&data))
		return -1;
	else
		return 0x0FF & data.byte;
}

static inline __s32 i2c_smbus_write_byte(int file, __u8 value)
{
	return i2c_smbus_access(file,I2C_SMBUS_WRITE,value,
	                        I2C_SMBUS_BYTE,NULL);
}

static inline __s32 i2c_smbus_read_byte_data(int file, __u8 command)
{
	union i2c_smbus_data data;
	if (i2c_smbus_access(file,I2C_SMBUS_READ,command,
	                     I2C_SMBUS_BYTE_DATA,&data))
		return -1;
	else
		return 0x0FF & data.byte;
}

static inline __s32 i2c_smbus_write_byte_data(int file, __u8 command, 
                                              __u8 value)
{
	union i2c_smbus_data data;
	data.byte = value;
	return i2c_smbus_access(file,I2C_SMBUS_WRITE,command,
	                        I2C_SMBUS_BYTE_DATA, &data);
}

static inline __s32 i2c_smbus_read_word_data(int file, __u8 command)
{
	union i2c_smbus_data data;
	if (i2c_smbus_access(file,I2C_SMBUS_READ,command,
	                     I2C_SMBUS_WORD_DATA,&data))
		return -1;
	else
		return 0x0FFFF & data.word;
}

static inline __s32 i2c_smbus_write_word_data(int file, __u8 command, 
                                              __u16 value)
{
	union i2c_smbus_data data;
	data.word = value;
	return i2c_smbus_access(file,I2C_SMBUS_WRITE,command,
	                        I2C_SMBUS_WORD_DATA, &data);
}

static inline __s32 i2c_smbus_process_call(int file, __u8 command, __u16 value)
{
	union i2c_smbus_data data;
	data.word = value;
	if (i2c_smbus_access(file,I2C_SMBUS_WRITE,command,
	                     I2C_SMBUS_PROC_CALL,&data))
		return -1;
	else
		return 0x0FFFF & data.word;
}


/* Returns the number of read bytes */
static inline __s32 i2c_smbus_read_block_data(int file, __u8 command, 
                                              __u8 *values)
{
	union i2c_smbus_data data;
	int i;
	if (i2c_smbus_access(file,I2C_SMBUS_READ,command,
	                     I2C_SMBUS_BLOCK_DATA,&data))
		return -1;
	else {
		for (i = 1; i <= data.block[0]; i++)
			values[i-1] = data.block[i];
		return data.block[0];
	}
}

static inline __s32 i2c_smbus_write_block_data(int file, __u8 command, 
                                               __u8 length, __u8 *values)
{
	union i2c_smbus_data data;
	int i;
	if (length > 32)
		length = 32;
	for (i = 1; i <= length; i++)
		data.block[i] = values[i-1];
	data.block[0] = length;
	return i2c_smbus_access(file,I2C_SMBUS_WRITE,command,
	                        I2C_SMBUS_BLOCK_DATA, &data);
}

/* Returns the number of read bytes */
/* Until kernel 2.6.22, the length is hardcoded to 32 bytes. If you
   ask for less than 32 bytes, your code will only work with kernels
   2.6.23 and later. */
static inline __s32 i2c_smbus_read_i2c_block_data(int file, __u8 command,
                                                  __u8 length, __u8 *values)
{
	union i2c_smbus_data data;
	int i;

	if (length > 32)
		length = 32;
	data.block[0] = length;
	if (i2c_smbus_access(file,I2C_SMBUS_READ,command,
	                     length == 32 ? I2C_SMBUS_I2C_BLOCK_BROKEN :
	                      I2C_SMBUS_I2C_BLOCK_DATA,&data))
		return -1;
	else {
		for (i = 1; i <= data.block[0]; i++)
			values[i-1] = data.block[i];
		return data.block[0];
	}
}

static inline __s32 i2c_smbus_write_i2c_block_data(int file, __u8 command,
                                               __u8 length, __u8 *values)
{
	union i2c_smbus_data data;
	int i;
	if (length > 32)
		length = 32;
	for (i = 1; i <= length; i++)
		data.block[i] = values[i-1];
	data.block[0] = length;
	return i2c_smbus_access(file,I2C_SMBUS_WRITE,command,
	                        I2C_SMBUS_I2C_BLOCK_BROKEN, &data);
}

/* Returns the number of read bytes */
static inline __s32 i2c_smbus_block_process_call(int file, __u8 command,
                                                 __u8 length, __u8 *values)
{
	union i2c_smbus_data data;
	int i;
	if (length > 32)
		length = 32;
	for (i = 1; i <= length; i++)
		data.block[i] = values[i-1];
	data.block[0] = length;
	if (i2c_smbus_access(file,I2C_SMBUS_WRITE,command,
	                     I2C_SMBUS_BLOCK_PROC_CALL,&data))
		return -1;
	else {
		for (i = 1; i <= data.block[0]; i++)
			values[i-1] = data.block[i];
		return data.block[0];
	}
}


#endif /* LIB_I2CDEV_H */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>

#include "i2c-dev.h"

#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

void swap_int16(short *val)
{
	*val = (*val << 8) | (*val >> 8);
}

int main(int argc, char *argv[])
{
	int fd;
	int ret;
	short accelx, accely, accelz;
	short temp;
	short gyrox, gyroy, gyroz;
	unsigned char *p;

	fd = open("/dev/i2c-5", O_RDWR);
	if (fd == -1)
		goto fail;

	if (ioctl(fd, I2C_SLAVE, 0x68) < 0)
		goto fail;

	i2c_smbus_write_byte_data(fd, PWR_MGMT_1, 0x80);
	usleep(200000);
	i2c_smbus_write_byte_data(fd, PWR_MGMT_1, 0x40);
	i2c_smbus_write_byte_data(fd, PWR_MGMT_1, 0x00);

	i2c_smbus_write_byte_data(fd, SMPLRT_DIV,   0x7);
	i2c_smbus_write_byte_data(fd, CONFIG,       0x6);
	i2c_smbus_write_byte_data(fd, GYRO_CONFIG,  0x3 << 3);
	i2c_smbus_write_byte_data(fd, ACCEL_CONFIG, 0x3 << 3);

	while (1) {
		accelx = i2c_smbus_read_word_data(fd, ACCEL_XOUT_H);
		swap_int16(&accelx);
		accely = i2c_smbus_read_byte_data(fd, ACCEL_YOUT_H);
		swap_int16(&accely);
		accelz = i2c_smbus_read_byte_data(fd, ACCEL_ZOUT_H);
		swap_int16(&accelz);

		printf("accelx: %.2f\n", accelx / 2048.0);
		printf("accely: %.2f\n", accely / 2048.0);
		printf("accelz: %.2f\n", accelz / 2048.0);

		temp = i2c_smbus_read_word_data(fd, TEMP_OUT_H);
		swap_int16(&temp);
		printf("temp: %.2f\n", temp / 340.0 + 36.53);

		gyrox = i2c_smbus_read_word_data(fd, GYRO_XOUT_H);
		swap_int16(&gyrox);
		gyroy = i2c_smbus_read_byte_data(fd, GYRO_YOUT_H);
		swap_int16(&gyroy);
		gyroz = i2c_smbus_read_byte_data(fd, GYRO_ZOUT_H);
		swap_int16(&gyroz);

		printf("gyrox: %.2f\n", gyrox / 16.4);
		printf("gyroy: %.2f\n", gyroy / 16.4);
		printf("gyroz: %.2f\n", gyroz / 16.4);

		sleep(1);
	}

fail:
	perror("i2c test");
	exit(EXIT_FAILURE);
}


        代码第 9 行包含了一个 i2c-dev.h 头文件,该文件中包含了 i2c_smbus_read_word_data等函数的定义。代码第 11 行至第 29 行是相关存器地址的宏定文。第45行开了/dev/i2c-5,因为根据前面的分析,MPU6050 是连接在 5号主机控制器上的。代码第 9行将 i2C 设备的地址设置为 0x68,也就是 MPU6050 的设备地址。代码第 52行复位整个芯片,然后休眠了 200ms,等待芯片复位完成,再将 SLEEP 位先置1后置0,确保其不处于 SLEEP 模式。代码第 57 行设置采样时钟分频值为7,第 8 行设置 DLPF_CFG 的值为6,那么采样率为 1KHz /(1 +7)= 125Hz。代码第 59 行和第 60行分别设置陀螺仪的的输出范围为土2000 和三轴加速度的输出范围为+16。代码第 60行至第 90行则读出采祥值.然后根据前面的换算关系进行换算并打印输出。
编译和测试的命令如下。



内核层 I2C 设备驱动代码如下

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>

#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>

#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/i2c.h>
#include <linux/delay.h>

#include "mpu6050.h"

#define FSRTC_MAJOR	256
#define FSRTC_MINOR	9
#define FSRTC_DEV_NAME	"mpu6050"

#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

struct mpu6050_dev {
	struct i2c_client *client;
	atomic_t available;
	struct cdev cdev;
};

static int mpu6050_open(struct inode *inode, struct file *filp)
{
	struct mpu6050_dev *mpu6050 = container_of(inode->i_cdev, struct mpu6050_dev, cdev);

	filp->private_data = mpu6050;
	if (atomic_dec_and_test(&mpu6050->available))
		return 0;
	else {
		atomic_inc(&mpu6050->available);
		return -EBUSY;
	}
}

static int mpu6050_release(struct inode *inode, struct file *filp)
{
	struct mpu6050_dev *mpu6050 = filp->private_data;

	atomic_inc(&mpu6050->available);
	return 0;
}

static long mpu6050_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct mpu6050_dev *mpu6050 = filp->private_data;
	struct atg_val val;

	if (_IOC_TYPE(cmd) != MPU6050_MAGIC)
		return -ENOTTY;

	switch (cmd) {
	case MPU6050_GET_VAL:
		val.accelx = i2c_smbus_read_word_data(mpu6050->client, ACCEL_XOUT_H);
		val.accely = i2c_smbus_read_word_data(mpu6050->client, ACCEL_YOUT_H);
		val.accelz = i2c_smbus_read_word_data(mpu6050->client, ACCEL_ZOUT_H);
		val.temp   = i2c_smbus_read_word_data(mpu6050->client, TEMP_OUT_H);
		val.gyrox  = i2c_smbus_read_word_data(mpu6050->client, GYRO_XOUT_H);
		val.gyroy  = i2c_smbus_read_word_data(mpu6050->client, GYRO_YOUT_H);
		val.gyroz  = i2c_smbus_read_word_data(mpu6050->client, GYRO_ZOUT_H);
		val.accelx = be16_to_cpu(val.accelx);
		val.accely = be16_to_cpu(val.accely);
		val.accelz = be16_to_cpu(val.accelz);
		val.temp   = be16_to_cpu(val.temp);
		val.gyrox  = be16_to_cpu(val.gyrox);
		val.gyroy  = be16_to_cpu(val.gyroy);
		val.gyroz  = be16_to_cpu(val.gyroz);
		if (copy_to_user((struct atg_val __user *)arg, &val, sizeof(struct atg_val)))
			return -EFAULT;
		break;
	default:
		return -ENOTTY;
	}

	return 0;
}

static struct file_operations mpu6050_ops = {
	.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 dev;
	struct mpu6050_dev *mpu6050;

	dev = MKDEV(FSRTC_MAJOR, FSRTC_MINOR);
	ret = register_chrdev_region(dev, 1, FSRTC_DEV_NAME);
	if (ret)
		goto reg_err;

	mpu6050 = kzalloc(sizeof(struct mpu6050_dev), GFP_KERNEL);
	if (!mpu6050) {
		ret = -ENOMEM;
		goto mem_err;
	}
	i2c_set_clientdata(client, mpu6050);
	mpu6050->client = client;

	cdev_init(&mpu6050->cdev, &mpu6050_ops);
	mpu6050->cdev.owner = THIS_MODULE;
	ret = cdev_add(&mpu6050->cdev, dev, 1);
	if (ret)
		goto add_err;

	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WORD_DATA)) {
		ret = -ENOSYS;
		goto fun_err;
	}

	i2c_smbus_write_byte_data(client, PWR_MGMT_1, 0x80);
	msleep(200);
	i2c_smbus_write_byte_data(client, PWR_MGMT_1, 0x40);
	i2c_smbus_write_byte_data(client, PWR_MGMT_1, 0x00);

	i2c_smbus_write_byte_data(client, SMPLRT_DIV,   0x7);
	i2c_smbus_write_byte_data(client, CONFIG,       0x6);
	i2c_smbus_write_byte_data(client, GYRO_CONFIG,  0x3 << 3); 
	i2c_smbus_write_byte_data(client, ACCEL_CONFIG, 0x3 << 3);

	atomic_set(&mpu6050->available, 1);

	return 0;

fun_err:
	cdev_del(&mpu6050->cdev);
add_err:
	kfree(mpu6050);
mem_err:
	unregister_chrdev_region(dev, 1);
reg_err:
	return ret;
}

static int mpu6050_remove(struct i2c_client *client)
{
	dev_t dev;
	struct mpu6050_dev *mpu6050 = i2c_get_clientdata(client);

	dev = MKDEV(FSRTC_MAJOR, FSRTC_MINOR);

	cdev_del(&mpu6050->cdev);
	kfree(mpu6050);
	unregister_chrdev_region(dev, 1);
	return 0;
}

static const struct i2c_device_id mpu6050_id[] = {
	{"mpu6050", 0},
	{}
};

MODULE_DEVICE_TABLE(i2c, mpu6050_id);

static struct i2c_driver mpu6050_driver = {
	.probe          =       mpu6050_probe,
	.remove         =       mpu6050_remove,
	.id_table       =       mpu6050_id,
	.driver = {
		.owner  =       THIS_MODULE,
		.name   =       "mpu6050",
	},
};

module_i2c_driver(mpu6050_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("MPU6050 driver");
#ifndef _MPU6050_H
#define _MPU6050_H

struct atg_val {
	short accelx;
	short accely;
	short accelz;
	short temp;
	short gyrox;
	short gyroy;
	short gyroz;
};

#define MPU6050_MAGIC	'm'

#define MPU6050_GET_VAL	_IOR(MPU6050_MAGIC, 0, struct atg_val)

#endif


        代码第 43 行至第 47 行定义了 struct mpu6050_dev 结构类型,成员 client 用于保存匹配的 client 对象。代码第 178 行至第 195 行是对 i2C 驱动的定义和注册相关的操作。代127 行将分配得到的 struct mpu050_dev 结构对象地址存入 client 中,方便之后从cliem中获取。代码第 136 行使用 i2c_check_functionality 验证 i2C 机控制器是否具有一个的随机读写能力。代码第 141 行至第 149 是对 MPU6050 的初始化,和前面应用层的 i2C没备驱动一样。mpu6050_ioctl 函数中读取寄存器的值,将大端转换成小端后复制给用户测试用的应用层代码比较简单以下是编译和测试的命令。
 

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>

#include "mpu6050.h"


int main(int argc, char *argv[])
{
	int fd;
	struct atg_val val;


	fd = open("/dev/mpu6050", O_RDWR);

	while (2) {
		ioctl(fd, MPU6050_GET_VAL, &val);

		printf("accelx: %.2f\n", val.accelx / 2048.0);
		printf("accely: %.2f\n", val.accely / 2048.0);
		printf("accelz: %.2f\n", val.accelz / 2048.0);
		printf("temp: %.2f\n", val.temp / 340.0 + 36.53); 
		printf("gyrox: %.2f\n", val.gyrox / 16.4);
		printf("gyroy: %.2f\n", val.gyroy / 16.4);
		printf("gyroz: %.2f\n", val.gyroz / 16.4);


		sleep(1);
	}
}

_____________________________________________________________________________

十分抱歉这篇文章晚了一个月,前段时间受感情影响导致工作和生活以及学习都不顺利,直接摆烂了两个星期,想想十分对不起老师和喜欢我文章的读者们,后面不会了,那个小太阳又回来了。

猜你喜欢

转载自blog.csdn.net/qq_52479948/article/details/133514984