ZYNQ学习笔记-IIC驱动的开发
linux中有着一个IIC子系统,为IIC设备驱动提供了抽象IIC接口。IIC与硬件相关的底层驱动由芯片厂商进行适配,普通用户只需要关注上层的抽象层即可完成驱动开发。
Linux中IIC设备的重要概念
关于IIC子系统,有几个重要的概念需要掌握。首先看一下IIC子系统的框图:

- I2C核心:提供抽象层代码的核心。
- I2C总线驱动:提供I2C通信能力。
- I2C设备驱动:提供设备的注册和设备驱动的注册。
对于用户来说,需要弄明白以下几个数据结构(均定义在include/linux/i2c.h 中):
- i2c_adapter结构体:对应I2C外设的抽象层,每个代表一个外设。
- i2c_algorithm结构体:对应I2C的传输方法,包含在i2c_adapter中,传输具体数据时调用的函数方法。
上面两者的具体实现方法由芯片公司提供的I2C总线驱动实现,普通用户无需知道细节。对普通用户来说,需要重点关注以下几个数据结构;
- i2c_client结构体:
struct i2c_client {
unsigned short flags; // 标志
unsigned short addr; // 芯片地址, 7 位,存在低 7 位
char name[I2C_NAME_SIZE]; // 设备名字
struct i2c_adapter *adapter; // 对应的 I2C 适配器
struct device dev; // 内置 device 结构体
int irq; // 中断
struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endif
}
一个I2C总线下的从设备对应一个i2c_client结构体变量。当驱动每匹配上一个设备时(通过设备树的compatible与of表匹配或者无设备树的i2c_device_id的name与I2C设备名字是否相同),系统就会分配一个client。
- i2c_driver结构体:
struct i2c_driver {
unsigned int class;
/* Notifies the driver that a new bus has appeared. You should avoid
* using this, it will be removed in a near future.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* New driver model interface to aid the seamless removal of the
* current probe()'s, more commonly unused than used second parameter.
*/
int (*probe_new)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
* For the SMBus Host Notify protocol, the data corresponds to the
* 16-bit payload data reported by the slave device acting as master.
*/
void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
unsigned int data);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
bool disable_i2c_core_irq_mapping;
};
这个结构体中,需要注意几个成员:
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
probe函数在设备匹配成功后会执行,每次匹配得到一个i2c_client。
remove函数在注销设备时使用,要注销的设备通过i2c_client传入。
struct device_driver driver;
const struct i2c_device_id *id_table;
两个成员设计IIC设备的匹配。传统匹配使用i2c_device_id表进行,使用设备树则用device_driver中的of_match_table 成员变量匹配compatible。
IIC驱动的单位是driver,IIC驱动加载时需要调用函数:
int i2c_register_driver(struct module *owner,struct i2c_driver *driver)
owner一般为THIS_MODULE。
与该函数对应有个宏帮助传递owner参数:
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
卸载驱动时需要调用注销:
void i2c_del_driver(struct i2c_driver *driver)
IIC驱动的单位是driver,这句话理解为,一个driver对应多个client,即一个驱动可以驱动多个同类型设备(基于匹配表)。每匹配到一个client,都需要在probe把字符设备创建过程跑一遍,在remove把注销过程跑一遍。
一般的驱动注册结构
对于一般用户,需要注册i2c_driver,一般的注册结构如下:
/* i2c 驱动的 probe 函数 */
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
/* 函数具体程序 */
return 0;
}
/* i2c 驱动的 remove 函数 */
static int xxx_remove(struct i2c_client *client)
{
/* 函数具体程序 */
return 0;
}
/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id xxx_id[] = {
{"xxx", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx" },
{ /* Sentinel */ }
};
/* i2c 驱动结构体 */
static struct i2c_driver xxx_driver = {
.probe = xxx_probe,
.remove = xxx_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxx",
.of_match_table = xxx_of_match,
},
.id_table = xxx_id,
};
/* 驱动入口函数 */
static int __init xxx_init(void)
{
int ret = 0;
ret = i2c_add_driver(&xxx_driver);
return ret;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
i2c_del_driver(&xxx_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
同时Linux也提供了一个宏来简化驱动注册:
module_i2c_driver(xxx_driver);
宏展开后是:
static int __init xxx_driver_init(void)
{
return i2c_add_driver(&xxx_driver);
}
module_init(xxx_driver_init);
static void __exit xxx_driver_exit(void)
{
i2c_del_driver(&xxx_driver);
}
module_exit(xxx_driver_exit);
可见方便了使用。
设备匹配方式
- 无设备树时匹配方式
老式匹配方式,这里不再多说。需要使用一个i2c_board_info 结构体来描述一个设备。
- 设备树添加方式
示例:
&i2c0 {
clock-frequency = <100000>;
rtc@51 {
compatible = "nxp,pcf8563";
reg = <0x51>;
};
};
对以上,驱动中的of_match表如下:
/* 匹配列表 */
static const struct of_device_id pcf8563_of_match[] = {
{ .compatible = "zynq-pcf8563" },
{ /* Sentinel */ }
};
实际数据传输函数
匹配到设备后需要利用IIC传输实际的数据,这里就涉及到一个函数:
int i2c_transfer(struct i2c_adapter *adap,
struct i2c_msg *msgs,
int num)
该函数最终会调用I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数 ,即IIC总线驱动中硬件相关传输函数。
该函数参数如下:
adap: 所使用的 I2C 适配器, i2c_client.i2c_adapter。
msgs: I2C 要发送的一个或多个消息。
num: 消息数量,也就是 msgs 的数量。
返回值: 负值,失败,其他非负值,发送的 msgs 数量。
i2c_msg定义在include/uapi/linux/i2c.h :
struct i2c_msg {
__u16 addr; /* 从机地址 */
__u16 flags; /* 标志 */
#define I2C_M_RD 0x0001 /* read data, from slave to master */
/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
__u16 len; /* 消息(数据)长度 */
__u8 *buf; /* 消息(数据)地址 */
};
传输前一般都会先构建好msg。
使用该函数读取I2C设备寄存器的例子:
/* 设备结构体 */
struct xxx_dev {
......
void *private_data; /* 私有数据,一般会设置为 i2c_client */
};
/**
* @description : 读取 I2C 设备多个寄存器数据
* @param – dev : I2C 设备
* @param – reg : 要读取的寄存器首地址
* @param – val : 读取到的数据
* @param – len : 要读取的数据长度
* @return : 操作结果
*/
static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;
/* msg[0],第一条写消息,发送要读取的寄存器首地址 */
msg[0].addr = client->addr; /* I2C 器件地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg 长度 */
/* msg[1],第二条读消息,读取寄存器数据 */
msg[1].addr = client->addr; /* I2C 器件地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度 */
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
ret = -EREMOTEIO;
}
return ret;
}
/*
* @description : 向 I2C 设备多个寄存器写入数据
* @param – dev : 要写入的设备结构体
* @param – reg : 要写入的寄存器首地址
* @param – val : 要写入的数据缓冲区
* @param – len : 要写入的数据长度
* @return : 操作结果
*/
static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组 b 里面 */
msg.addr = client->addr; /* I2C 器件地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要发送的数据缓冲区 */
msg.len = len + 1; /* 要发送的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
除此之外还有两个API函数用来收发数据,最后都会调用i2c_transfer。
int i2c_master_send(const struct i2c_client *client,
const char *buf,
int count)
int i2c_master_recv(const struct i2c_client *client,
char *buf,
int count)
count: 要接收的数据字节数,要小于 64KB,因为 i2c_msg 的 len 成员变量是一个 u16(无
符号 16 位)类型的数据。
实战-MPU6050驱动的编写
首先,在设备树中添加MPU6050节点:
&i2c0{
clock-frequency=<100000>;
mpu6050@68{
compatible = "atk,mpu6050";
reg=<0x68>;
};
};
驱动中编写如下代码:
#include "mpu6050.h"
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/fs.h>
#define MPU6050_CNT 1
#define MPU6050_NAME "mpu6050"
struct mpu6050_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
void *private_data; /* 私有数据用于存储client*/
signed int gyro_x_adc; /* 陀螺仪X轴原始值 */
signed int gyro_y_adc; /* 陀螺仪Y轴原始值 */
signed int gyro_z_adc; /* 陀螺仪Z轴原始值 */
signed int accel_x_adc; /* 加速度计X轴原始值 */
signed int accel_y_adc; /* 加速度计Y轴原始值 */
signed int accel_z_adc; /* 加速度计Z轴原始值 */
signed int temp_adc; /* 温度原始值 */
};
static struct mpu6050_dev mpu6050dev;
static int mpu6050_release(struct inode *inode, struct file *filp)
{
return 0;
}
static int mpu6050_open(struct inode *inode, struct file *filp)
{
filp->private_data = &mpu6050dev; /* 设置私有数据 */
return 0;
}
static s32 mpu6050_write_regs(struct mpu6050_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->private_data;
b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */
msg.addr = client->addr; /* mpu6050地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要写入的数据缓冲区 */
msg.len = len + 1; /* 要写入的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
static void mpu6050_write_onereg(struct mpu6050_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
mpu6050_write_regs(dev, reg, &buf, 1);
}
static int mpu6050_read_regs(struct mpu6050_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->private_data;
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /* */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg长度*/
/* msg[1]读取数据 */
msg[1].addr = client->addr; /* */
msg[1].flags = I2C_M_RD; /* 标记为读取数据*/
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度*/
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
static unsigned char mpu6050_read_onereg(struct mpu6050_dev *dev, u8 reg)
{
u8 data = 0;
mpu6050_read_regs(dev, reg, &data, 1);
return data;
}
void mpu6050_readdata(struct mpu6050_dev *dev)
{
unsigned char data[14];
mpu6050_read_regs(dev, MPU6050_RA_ACCEL_XOUT_H, data, 14);
dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);
dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);
dev->temp_adc = (signed short)((data[6] << 8) | data[7]);
dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]);
dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);
dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
}
static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
signed int data[7];
long err = 0;
struct mpu6050_dev *dev = (struct mpu6050_dev *)filp->private_data;
mpu6050_readdata(dev);
data[0] = dev->gyro_x_adc;
data[1] = dev->gyro_y_adc;
data[2] = dev->gyro_z_adc;
data[3] = dev->accel_x_adc;
data[4] = dev->accel_y_adc;
data[5] = dev->accel_z_adc;
data[6] = dev->temp_adc;
//printk("mpu6050 Read Data:x_adc:%d\r\n",data[0]);
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
/* mpu6050操作函数 */
static const struct file_operations mpu6050_ops = {
.owner = THIS_MODULE,
.open = mpu6050_open,
.read = mpu6050_read,
.release = mpu6050_release,
};
/**
* mpu6050初始化函数
*/
void mpu6050_reginit(void)
{
u8 value = 0;
mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_PWR_MGMT_1, 0x80);//复位设备
mdelay(50);
mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_PWR_MGMT_1, 0x01);//退出休眠模式,选择X轴PLL作为时钟
mdelay(50);
value = mpu6050_read_onereg(&mpu6050dev, MPU6050_RA_WHO_AM_I);
printk("mpu6050 ID = %#X\r\n", value);
mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_SMPLRT_DIV, 0x00); /* 输出速率是内部采样率 */
//设置量程为最小,提高精度
mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_GYRO_CONFIG, 0x00); /* 陀螺仪±250dps量程 */
mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_ACCEL_CONFIG, 0x00); /* 加速度计±2G量程 */
// mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_GYRO_CONFIG, 0x18); /* 陀螺仪±2000dps量程 */
// mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_ACCEL_CONFIG, 0x18); /* 加速度计±16G量程 */
mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_CONFIG, 0x04); /* 陀螺仪低通滤波BW=20Hz */
mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_PWR_MGMT_2, 0x00); /* 打开加速度计和陀螺仪所有轴 */
mpu6050_write_onereg(&mpu6050dev, MPU6050_RA_FIFO_EN, 0x00); /* 关闭FIFO */
}
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
/* 1、构建设备号 */
if (mpu6050dev.major) {
mpu6050dev.devid = MKDEV(mpu6050dev.major, 0);
ret=register_chrdev_region(mpu6050dev.devid, MPU6050_CNT, MPU6050_NAME);
} else {
ret=alloc_chrdev_region(&mpu6050dev.devid, 0, MPU6050_CNT, MPU6050_NAME);
mpu6050dev.major = MAJOR(mpu6050dev.devid);
}
if(ret) return ret;
/* 2、注册cdev设备 */
cdev_init(&mpu6050dev.cdev, &mpu6050_ops);
ret=cdev_add(&mpu6050dev.cdev, mpu6050dev.devid, MPU6050_CNT);
if(ret) goto out1;
/* 3、创建类 */
mpu6050dev.class = class_create(THIS_MODULE, MPU6050_NAME);
if (IS_ERR(mpu6050dev.class)) {
ret=PTR_ERR(mpu6050dev.class);
goto out2;
}
/* 4、创建设备 */
mpu6050dev.device = device_create(mpu6050dev.class, &client->dev, mpu6050dev.devid, NULL, MPU6050_NAME);
if (IS_ERR(mpu6050dev.device)) {
ret = PTR_ERR(mpu6050dev.device);
goto out3;
}
mpu6050dev.private_data = client; /* 设置私有数据 */
mpu6050_reginit();
return 0;
//分级进行反注册设备
out3:
class_destroy(mpu6050dev.class);
out2:
cdev_del(&mpu6050dev.cdev);
out1:
unregister_chrdev_region(mpu6050dev.devid,MPU6050_CNT);
return ret;
}
static int mpu6050_remove(struct i2c_client *client)
{
/* 删除设备 */
cdev_del(&mpu6050dev.cdev);
unregister_chrdev_region(mpu6050dev.devid, MPU6050_CNT);
/* 注销掉类和设备 */
device_destroy(mpu6050dev.class, mpu6050dev.devid);
class_destroy(mpu6050dev.class);
return 0;
}
/* 传统匹配方式ID列表 */
static const struct i2c_device_id mpu6050_id[] = {
{"atk,mpu6050", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id mpu6050_of_match[] = {
{ .compatible = "atk,mpu6050" },
{ /* Sentinel */ }
};
static struct i2c_driver mpu6050_driver = {
.probe = mpu6050_probe,//匹配到设备时调用
.remove = mpu6050_remove,
.driver = {
.owner = THIS_MODULE,
.name = "mpu6050",
.of_match_table = mpu6050_of_match,//设备匹配表
},
.id_table = mpu6050_id,
};
module_i2c_driver(mpu6050_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LO");
编写测试APP:
#include "stdio.h"
#include "unistd.h"
#include "fcntl.h"
int main(int argc, char *argv[])
{
int i=10;
int fd;
signed int buffer[7];
signed int gyro_x_adc, gyro_y_adc, gyro_z_adc;
signed int accel_x_adc, accel_y_adc, accel_z_adc;
signed int temp_adc;
float gyro_x_act, gyro_y_act, gyro_z_act;
float accel_x_act, accel_y_act, accel_z_act;
float temp_act;
int ret = 0;
char* filename = "/dev/mpu6050";
fd = open(filename, O_RDWR);
if(fd < 0) {
printf("can't open file %s\r\n", filename);
return -1;
}
printf("Start Read.\r\n");
while (i--) {
ret = read(fd, buffer, sizeof(buffer));
if(ret == 0) {
gyro_x_adc = buffer[0];
gyro_y_adc = buffer[1];
gyro_z_adc = buffer[2];
accel_x_adc = buffer[3];
accel_y_adc = buffer[4];
accel_z_adc = buffer[5];
temp_adc = buffer[6];
gyro_x_act = (float)(gyro_x_adc) / 16.4;
gyro_y_act = (float)(gyro_y_adc) / 16.4;
gyro_z_act = (float)(gyro_z_adc) / 16.4;
accel_x_act = (float)(accel_x_adc) / 2048;
accel_y_act = (float)(accel_y_adc) / 2048;
accel_z_act = (float)(accel_z_adc) / 2048;
temp_act = ((float)(temp_adc)/ 340 ) + 36.53;
printf("\r\nADC value:\r\n");
printf("gx = %d, gy = %d, gz = %d\r\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);
printf("ax = %d, ay = %d, az = %d\r\n", accel_x_adc, accel_y_adc, accel_z_adc);
printf("temp = %d\r\n", temp_adc);
printf("TRUE value:");
printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", gyro_x_act, gyro_y_act, gyro_z_act);
printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", accel_x_act, accel_y_act, accel_z_act);
printf("act temp = %.2f°C\r\n", temp_act);
}
usleep(1000000);
}
close(fd);
return 0;
}

浙公网安备 33010602011771号