i2c 总线是一种十分常见的板级总线,本文以 linux3.14.0 为参考, 讨论 Linux 中的 i2c 驱动模型并利用这个模型写一个 mpu6050 的驱动, 最后在应用层将 mpu6050 中的原始数据读取出来
下图就是我理解的 i2c 驱动框架示意图, 类似中断子系统, i2c 子系统中也使用一个对象来描述一个物理实体, 设备对象与驱动分离, 驱动结合设备对象对硬件设备的描述才可以驱动一个具体的物理设备, 体现了分离的设计思想, 实现了代码的复用, 比如:
事实上, 对于任何一种总线, 内核都有一个 bus_type 类型的对象与之对应, 但是 platform_bus_type 并没有对应的实际的物理总线, 这也就是 platform 总线也叫虚拟总线的原因.
除了分离,i2c 子系统也体现的软件分层的设计思想, 整个 i2c 子系统由 3 层构成:设备驱动层 --i2c 核心 -- 控制器驱动
除了经典的分层与分离模型,我们也可以看到一个有意思的现象——Linux 的应用程序不但可以通过设备驱动来访问 i2c 从设备,还可以通过一个并没有直接挂接到 i2c_bus_type 的 i2c_cleint 来找到主机控制器进而访问任意一个 i2c 设备, 这是怎么回事呢? 我会在下一篇说 ^-^
我首先说 i2c_adapter, 并不是编写一个 i2c 设备驱动需要它, 通常我们在配置内核的时候已经将 i2c 控制器的设备信息和驱动已经编译进内核了, 就是这个 adapter 对象已经创建好了, 但是了解其中的成员对于理解 i2c 驱动框架非常重要, 所有的设备驱动都要经过这个对象的处理才能和物理设备通信
- //include/linux/i2c.h
- 425 struct i2c_adapter {
- 426 struct module * owner;
- 427 unsigned int class;
- /* classes to allow probing for */
- 428 const struct i2c_algorithm * algo;
- /* the algorithm to access the bus */
- 429 void * algo_data;
- 430 431
- /* data fields that are valid for all devices */
- 432 struct rt_mutex bus_lock;
- 433 434 int timeout;
- /* in jiffies */
- 435 int retries;
- 436 struct device dev;
- /* the adapter device */
- 437 438 int nr;
- 439 char name[48];
- 440 struct completion dev_released;
- 441 442 struct mutex userspace_clients_lock;
- 443 struct list_head userspace_clients;
- 444 445 struct i2c_bus_recovery_info * bus_recovery_info;
- 446
- };
下面是 2 个 i2c-core.c 提供的 i2c_adapter 直接相关的操作 API, 通常也不需要设备驱动开发中使用,
这个 API 可以将一个 i2c_adapter 类型的对象注册到内核中, 源码我就不贴了, 下面是他们的调用关系, 我们可以从中看到一个 adapter 对象和系统中的 i2c_driver 对象以及 i2c_client 对象的匹配流程。
首先,我们在驱动中构造一个 i2c_adapter 对象的时候,对其中的相关域进行初始化,这里我们最关心它的父设备
- //drivers/i2c/buses/i2c-s3c2410.c
- 1072 static int s3c24xx_i2c_probe(struct platform_device *pdev)
- 1073 {
- 1140 i2c->adap.dev.parent = &pdev->dev;
- 1210 }
得到了这样一个 i2c_adapter 对象,我们就可以调用这个 API 将它注册到内核,调用关系如下:
调用关系就是这样了,下面我简单解释一下这个树
从内核中删除一个 adapter
在 i2c 设备端,驱动开发的主要工作和平台总线一样: 构建设备对象和驱动对象,我用的开发板上的 i2c 总线上挂接的设备是 mpu6050,接下来我就以我的板子为例,讨论如何编写 i2c 设备端的驱动。
同样这里的设备对象也可以使用三种方式构建: 平台文件,模块和设备树。
本文采用设备树的方式构建设备对象,我们可以参考内核文档 "Documentations/devicetree/bindings/i2c/i2c-s3c2410.txt" 以及设备树中的样板来编写我们的设备树节点,** 我们在设备树中可不会写 mpu6050 内部寄存器的地址, 因为这些寄存器地址 SoC 看不到 **。
写了这个设备节点,内核就会为我们在内核中构造一个 i2c_client 对象并挂接到 i2c 总线对象的设备链表中以待匹配,这个设备类如下
- //include/linux/i2c.h
- 217 struct i2c_client {
- 218 unsigned short flags;
- /* div., see below */
- 219 unsigned short addr;
- /* chip address - NOTE: 7bit */
- 220
- /* addresses are stored in the */
- 221
- /* _LOWER_ 7 bits */
- 222 char name[I2C_NAME_SIZE];
- 223 struct i2c_adapter * adapter;
- /* the adapter we sit on */
- 224 struct device dev;
- /* the device structure */
- 225 int irq;
- /* irq issued by device */
- 226 struct list_head detected;
- 227
- };
和平台总线类似,i2c 驱动对象使用 i2c_driver 结构来描述,所以,编写一个 i2c 驱动的本质工作就是构造一个 i2c_driver 对象并将其注册到内核。我们先来认识一下这个对象
- //include/linux/i2c.h
- 161 struct i2c_driver {
- 162 unsigned int class;
- 167 int( * attach_adapter)(struct i2c_adapter * ) __deprecated;
- 170 int( * probe)(struct i2c_client * , const struct i2c_device_id * );
- 171 int( * remove)(struct i2c_client * );
- 174 void( * shutdown)(struct i2c_client * );
- 175 int( * suspend)(struct i2c_client * , pm_message_t mesg);
- 176 int( * resume)(struct i2c_client * );
- 183 void( * alert)(struct i2c_client * , unsigned int data);
- 188 int( * command)(struct i2c_client * client, unsigned int cmd, void * arg);
- 190 struct device_driver driver;
- 191 const struct i2c_device_id * id_table;
- 194 int( * detect)(struct i2c_client * , struct i2c_board_info * );
- 195 const unsigned short * address_list;
- 196 struct list_head clients;
- 197
- };
那么接下来就是填充对象了,我们这里使用的是设备树匹配,所以 of_match_table 被填充如下。
- struct of_device_id mpu6050_dt_match[] = {
- {.compatible = "invensense,mpu6050"
- },
- {},
- };
- struct i2c_device_id mpu6050_dev_match[] = {};
然后将这两个成员填充到 i2c_driver 对象如下,这个阶段我们可以在 mpu6050_probe 中只填写 prink 来测试我们的驱动方法对象是否有问题。
- struct i2c_driver mpu6050_driver = {
- .probe = mpu6050_probe,
- .remove = mpu6050_remove,
- .driver = {
- .owner = THIS_MODULE,
- .name = "mpu6050drv",
- .of_match_table = of_match_ptr(mpu6050_dt_match),
- },
- .id_table = mpu6050_dev_match,
- };
使用下述 API 注册 / 注销驱动对象,这个宏和 module_platform_driver 一样是内核提供给我们一个用于快速实现注册注销接口的快捷方式,写了这句以及模块授权,我们就可以静待各种信息被打印了
- module_i2c_driver(mpu6050_driver);
如果测试通过,我们就要研究如何找到 adapter 以及如何通过找到的 adapter 将数据发送出去。没错,我说的 i2c_msg
- 68 struct i2c_msg {
- 69 __u16 addr;
- /* slave address */
- 70 __u16 flags;
- 71#define I2C_M_TEN 0x0010
- /* this is a ten bit chip address */
- 72#define I2C_M_RD 0x0001
- /* read data, from slave to master */
- 73#define I2C_M_STOP 0x8000
- /* if I2C_FUNC_PROTOCOL_MANGLING */
- 74#define I2C_M_NOSTART 0x4000
- /* if I2C_FUNC_NOSTART */
- 75#define I2C_M_REV_DIR_ADDR 0x2000
- /* if I2C_FUNC_PROTOCOL_MANGLING */
- 76#define I2C_M_IGNORE_NAK 0x1000
- /* if I2C_FUNC_PROTOCOL_MANGLING */
- 77#define I2C_M_NO_RD_ACK 0x0800
- /* if I2C_FUNC_PROTOCOL_MANGLING */
- 78#define I2C_M_RECV_LEN 0x0400
- /* length will be first received byte */
- 79 __u16 len;
- /* msg length */
- 80 __u8 * buf;
- /* pointer to msg data */
- 81
- };
我们知道,i2c 总线上传入数据是以字节为单位的,而我们的通信类别分为两种: 读 and 写,对于写,通常按照下面的时序
Mater | S | I2CAddr+WriteBit | InternalRegisterAddr | DATA | DATA | P | ||||
---|---|---|---|---|---|---|---|---|---|---|
Slave | ACK | ACK | ACK | ACK |
对于读,通常是按照下面的时序
Mater | S | I2CAddr+WriteBit | InternalRegisterAddr | S | I2CAddr+ReadBit | ACK | NACK | P | |||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Slave | ACK | ACK | ACK | DATA | DATA |
i2c 子系统为了实现这种通信方法,为我们封装了 i2c_msg 结构,对于每一个 START 信号,都对应一个 i2c_msg 对象,实际操作中我们会将所有的请求封装成一个 struct i2c_msg[],一次性将所有的请求通过 i2c_transfer() 发送给匹配到的 client 的从属的 adapter,由 adapter 根据相应的 algo 域以及 master_xfer 域通过主机驱动来将这些请求发送给硬件上的设备
这是一个通过 i2c 总线来访问 mpu6050 的驱动
- //mpu6050_common.h
- #define MPU6050_MAGIC 'K'
- union mpu6050_data
- {
- struct {
- short x;
- short y;
- short z;
- }accel;
- struct {
- short x;
- short y;
- 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)
- //mpu6050_drv.h
- #define SMPLRT_DIV 0x19 //陀螺仪采样率,典型值:0x07(125Hz)
- #define CONFIG 0x1A //低通滤波频率,典型值:0x06(5Hz)
- #define GYRO_CONFIG 0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
- #define ACCEL_CONFIG 0x1C //加速计自检、测量范围及高通滤波,典型值:0x18(不自检,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 //陀螺仪z轴角速度数据寄存器(高位)
- #define GYRO_ZOUT_L 0x48 //陀螺仪z轴角速度数据寄存器(低位)
- #define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用)
- #define WHO_AM_I 0x75 //IIC地址寄存器(默认数值0x68,只读)
- #define SlaveAddress 0x68 //MPU6050-I2C地址寄存器
- #define W_FLG 0
- #define R_FLG 1
- //mpu6050.c
- struct mpu6050_pri {
- struct cdev dev;
- struct i2c_client *client;
- };
- struct mpu6050_pri dev;
- static void mpu6050_write_byte(struct i2c_client *client,const unsigned char reg,const unsigned char val)
- {
- char txbuf[2] = {reg,val};
- struct i2c_msg msg[2] = {
- [0] = {
- .addr = client->addr,
- .flags= W_FLG,
- .len = sizeof(txbuf),
- .buf = txbuf,
- },
- };
- i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
- }
- static char mpu6050_read_byte(struct i2c_client *client,const unsigned char reg)
- {
- char txbuf[1] = {reg};
- char rxbuf[1] = {0};
- struct i2c_msg msg[2] = {
- [0] = {
- .addr = client->addr,
- .flags = W_FLG,
- .len = sizeof(txbuf),
- .buf = txbuf,
- },
- [1] = {
- .addr = client->addr,
- .flags = I2C_M_RD,
- .len = sizeof(rxbuf),
- .buf = rxbuf,
- },
- };
- i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
- return rxbuf[0];
- }
- static int dev_open(struct inode *ip, struct file *fp)
- {
- return 0;
- }
- static int dev_release(struct inode *ip, struct file *fp)
- {
- return 0;
- }
- static long dev_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
- {
- int res = 0;
- union mpu6050_data data = {{0}};
- switch(cmd){
- case GET_ACCEL:
- data.accel.x = mpu6050_read_byte(dev.client,ACCEL_XOUT_L);
- data.accel.x|= mpu6050_read_byte(dev.client,ACCEL_XOUT_H)<<8;
- data.accel.y = mpu6050_read_byte(dev.client,ACCEL_YOUT_L);
- data.accel.y|= mpu6050_read_byte(dev.client,ACCEL_YOUT_H)<<8;
- data.accel.z = mpu6050_read_byte(dev.client,ACCEL_ZOUT_L);
- data.accel.z|= mpu6050_read_byte(dev.client,ACCEL_ZOUT_H)<<8;
- break;
- case GET_GYRO:
- data.gyro.x = mpu6050_read_byte(dev.client,GYRO_XOUT_L);
- data.gyro.x|= mpu6050_read_byte(dev.client,GYRO_XOUT_H)<<8;
- data.gyro.y = mpu6050_read_byte(dev.client,GYRO_YOUT_L);
- data.gyro.y|= mpu6050_read_byte(dev.client,GYRO_YOUT_H)<<8;
- data.gyro.z = mpu6050_read_byte(dev.client,GYRO_ZOUT_L);
- data.gyro.z|= mpu6050_read_byte(dev.client,GYRO_ZOUT_H)<<8;
- printk("gyro:x %d, y:%d, z:%d\n",data.gyro.x,data.gyro.y,data.gyro.z);
- break;
- case GET_TEMP:
- data.temp = mpu6050_read_byte(dev.client,TEMP_OUT_L);
- data.temp|= mpu6050_read_byte(dev.client,TEMP_OUT_H)<<8;
- printk("temp: %d\n",data.temp);
- break;
- default:
- printk(KERN_INFO "invalid cmd");
- break;
- }
- printk("acc:x %d, y:%d, z:%d\n",data.accel.x,data.accel.y,data.accel.z);
- res = copy_to_user((void *)arg,&data,sizeof(data));
- return sizeof(data);
- }
- struct file_operations fops = {
- .open = dev_open,
- .release = dev_release,
- .unlocked_ioctl = dev_ioctl,
- };
- #define DEV_CNT 1
- #define DEV_MI 0
- #define DEV_MAME "mpu6050"
- struct class *cls;
- dev_t dev_no ;
- static void mpu6050_init(struct i2c_client *client)
- {
- 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, 0x18);
- mpu6050_write_byte(client, ACCEL_CONFIG, 0x0);
- }
- static int mpu6050_probe(struct i2c_client * client, const struct i2c_device_id * id)
- {
- dev.client = client;
- printk(KERN_INFO "xj_match ok\n");
- cdev_init(&dev.dev,&fops);
- alloc_chrdev_region(&dev_no,DEV_MI,DEV_CNT,DEV_MAME);
- cdev_add(&dev.dev,dev_no,DEV_CNT);
- mpu6050_init(client);
- /*自动创建设备文件*/
- cls = class_create(THIS_MODULE,DEV_MAME);
- device_create(cls,NULL,dev_no,NULL,"%s%d",DEV_MAME,DEV_MI);
- printk(KERN_INFO "probe\n");
- return 0;
- }
- static int mpu6050_remove(struct i2c_client * client)
- {
- device_destroy(cls,dev_no);
- class_destroy(cls);
- unregister_chrdev_region(dev_no,DEV_CNT);
- return 0;
- }
- struct of_device_id mpu6050_dt_match[] = {
- {.compatible = "invensense,mpu6050"},
- {},
- };
- struct i2c_device_id mpu6050_dev_match[] = {};
- struct i2c_driver mpu6050_driver = {
- .probe = mpu6050_probe,
- .remove = mpu6050_remove,
- .driver = {
- .owner = THIS_MODULE,
- .name = "mpu6050drv",
- .of_match_table = of_match_ptr(mpu6050_dt_match),
- },
- .id_table = mpu6050_dev_match,
- };
- module_i2c_driver(mpu6050_driver);
- MODULE_LICENSE("GPL");
通过上面的驱动, 我们可以在应用层操作设备文件从 mpu6050 寄存器中读取原始数据, 应用层如下
- int main(int argc, char * const argv[]) {
- int fd = open(argv[1], O_RDWR);
- if ( - 1 == fd) {
- perror("open");
- return - 1;
- }
- union mpu6050_data data = {
- {
- 0
- }
- };
- while (1) {
- ioctl(fd, GET_ACCEL, &data);
- printf("acc:x %d, y:%d, z:%d\n", data.accel.x, data.accel.y, data.accel.z);
- ioctl(fd, GET_GYRO, &data);
- printf("gyro:x %d, y:%d, z:%d\n", data.gyro.x, data.gyro.y, data.gyro.z);
- ioctl(fd, GET_TEMP, &data);
- printf("temp: %d\n", data.temp);
- sleep(1);
- }
- return 0;
- }
最终可以获取传感器的原始数据如下
来源: http://www.cnblogs.com/xiaojiang1025/p/6500540.html