ZYNQ学习笔记-IIC驱动的开发

linux中有着一个IIC子系统,为IIC设备驱动提供了抽象IIC接口。IIC与硬件相关的底层驱动由芯片厂商进行适配,普通用户只需要关注上层的抽象层即可完成驱动开发。

Linux中IIC设备的重要概念

关于IIC子系统,有几个重要的概念需要掌握。首先看一下IIC子系统的框图:
image

  • 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 = &reg; /* 读取的首地址 */
 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 = &reg;					/* 读取的首地址 */
    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;
}

posted @ 2026-08-22 12:33  LO_StacNet  阅读(0)  评论(0)    收藏  举报