跳转至

14.Linux I2C驱动

I2C 总线驱动,或者说 I2C 适配器驱动的主要工作就是初始化 i2c_adapter 结构体变量,然后设置 i2c_algorithm 中的 master_xfer 函数。完成以后通过i2c_add_numbered_adapter或 i2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapter

i2c是有自己的总线驱动的,不需要像platform一样添加总线

我是使用的正点原子mini开发板,上面没有i2c连接的设备,我连接一个mpu6050来做测试。

驱动程序

修改设备树,在i2c设备中,添加mpu6050节点。

&i2c1 {
    clock-frequency = <100000>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";

    mpu6050@68 {
        compatible = "alientek,mpu6050";
        reg = <0x68>;
    };
};

包含所需头文件

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

然后写驱动出入口相关函数,这里知道i2c_add_driver是添加一个iic设备,i2c_del_driver是卸载。这里i2c驱动结构体结构体还未定义。

static int __init mpu6050_init(void)
{
    int ret = 0;

    ret = i2c_add_driver(&mpu6050_driver);
    return ret;
}

static void __exit mpu6050_exit(void)
{
    i2c_del_driver(&mpu6050_driver);
}

module_init(mpu6050_init);
module_exit(mpu6050_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liqinghua");

然后完善设备树匹配列表和i2c驱动结构体结构体。结构体内部相关函数未声明。传统和设备树匹配列表必须同时存在,不然无法进入probe函数。

/* 传统匹配方式ID列表 */
static const struct i2c_device_id mpu6050_id[] = {
    {"alientek,mpu6050", 0},  
    {}
};

/* 设备树匹配列表 */
static const struct of_device_id mpu6050_of_match[] = {
    { .compatible = "alientek,mpu6050" },
    { /* Sentinel */ }
};

/* i2c驱动结构体 */  
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,
};

然后完善probe和remove函数,这里主要是注册设备,以及删除设备

#define MPU_DEV_MAJOR        0
#define MPU_DEV_NAME         "mpu6050"
#define MPU_DEV_CLASS_NAME   "mpu6050_class"
#define MPU_DEV_NODE_NAME    "mpu6050" 

struct mpu6050_dev_t{   
    struct class *class;        
    struct device *device;
    struct device_node  *nd;
    struct i2c_client *client;
    int16_t acc[3];
    int16_t gyro[3];
    int16_t temp;
    int major;              
    int minor;              
};

struct mpu6050_dev_t mpu6050_dev;

static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret;

    ret = register_chrdev(MPU_DEV_MAJOR, MPU_DEV_NAME, &mpu_drv_fops);

    if(ret < 0){
        printk("hello driver register failed\r\n");
        return ret;
    }

    if(0 == MPU_DEV_MAJOR)
    {
        mpu6050_dev.major = ret;
    }else
    {
        mpu6050_dev.major = MPU_DEV_MAJOR;
    }
    printk("led.major = %d\r\n",mpu6050_dev.major);

    //创建类
    mpu6050_dev.class = class_create(THIS_MODULE, MPU_DEV_CLASS_NAME);
    if (IS_ERR(mpu6050_dev.class)) {
        return PTR_ERR(mpu6050_dev.class);
    }
    //创建设备
    mpu6050_dev.device = device_create(lempu6050_devd.class, NULL, MKDEV(mpu6050_dev.major, 0), NULL, MPU_DEV_NODE_NAME); 
    if (IS_ERR(mpu6050_dev.device)) {
        return PTR_ERR(mpu6050_dev.device);
    }

    mpu6050_dev.client = client;

    return 0;
}

static int mpu6050_remove(struct i2c_client *client)
{
    unregister_chrdev(mpu6050_dev.major, MPU_DEV_NAME);

    //删除类、删除设备
    device_destroy(mpu6050_dev.class, MKDEV(mpu6050_dev.major, 0));
    class_destroy(mpu6050_dev.class);
    return 0;
}

然后编写文件操作函数

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

    return 0;
}

static ssize_t mpu6050_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
    int16_t data[7];
    long err = 0;

    struct mpu6050_dev_t *dev = (struct mpu6050_dev_t *)filp->private_data;

    mpu6050_read_data(dev);

    data[0] = dev->acc[0];
    data[1] = dev->acc[1];
    data[2] = dev->acc[2];
    data[3] = dev->gyro[0];
    data[4] = dev->gyro[1];
    data[5] = dev->gyro[2];
    data[6] = dev->temp;
    err = copy_to_user(buf, data, sizeof(data));

    return 0;
}


static int mpu6050_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* mpu6050操作函数 */
static const struct file_operations mpu_drv_fops = {
    .owner = THIS_MODULE,
    .open = mpu6050_open,
    .read = mpu6050_read,
    .release = mpu6050_release,
};

编写i2c的write和read函数

static int mpu6050_read_regs(struct mpu6050_dev_t *dev, u8 reg, void *val, int len)
{
    int ret;
    struct i2c_msg msg[2];
    struct i2c_client *client = (struct i2c_client *)dev->client;

    /* msg[0]为发送要读取的首地址 */
    msg[0].addr = client->addr;         /* mpu6050地址 */
    msg[0].flags = 0;                   /* 标记为发送数据 */
    msg[0].buf = &reg;                  /* 读取的首地址 */
    msg[0].len = 1;                     /* reg长度*/

    /* msg[1]读取数据 */
    msg[1].addr = client->addr;         /* mpu6050地址 */
    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 s32 mpu6050_write_regs(struct mpu6050_dev_t *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);
}

然后就是关于mpu6050的初始化和读取代码了,这一部分,大部分是移植的正点原子的代码

//设置陀螺仪传感器满量程范围
//fsr:0,±250dps;1,±500dps;2,±1000dps;3,±2000dps
//返回值:0,设置成功
//    其他,设置失败
uint8_t MPU_Set_Gyro_Fsr(uint8_t fsr)
{
    uint8_t fsr_temp;
    fsr_temp = (fsr<<3)|3;
    return mpu6050_write_regs(&mpu6050_dev,MPU_GYRO_CFG_REG,&fsr_temp,1);//设置陀螺仪满量程范围
}
//设置加速度传感器满量程范围
//fsr:0,±2g;1,±4g;2,±8g;3,±16g
//返回值:0,设置成功
//    其他,设置失败
uint8_t MPU_Set_Accel_Fsr(uint8_t fsr)
{
    uint8_t fsr_temp;
    fsr_temp = (fsr<<3);
    return mpu6050_write_regs(&mpu6050_dev,MPU_ACCEL_CFG_REG,&fsr_temp,1);//设置加速度传感器满量程范围
}

//设置数字低通滤波器
//lpf:数字低通滤波频率(Hz)
//返回值:0,设置成功
//    其他,设置失败
uint8_t MPU_Set_LPF(uint16_t lpf)
{
    uint8_t data=0;
    if(lpf>=188)data=1;
    else if(lpf>=98)data=2;
    else if(lpf>=42)data=3;
    else if(lpf>=20)data=4;
    else if(lpf>=10)data=5;
    else data=6;
    return mpu6050_write_regs(&mpu6050_dev,MPU_CFG_REG,&data,1);//设置数字低通滤波器
}

//设置采样率(假定Fs=1KHz)
//rate:4~1000(Hz)
//返回值:0,设置成功
//    其他,设置失败
uint8_t MPU_Set_Rate(uint16_t rate)
{
    uint8_t data;
    if(rate>1000)rate=1000;
    if(rate<4)rate=4;
    data=1000/rate-1;
    mpu6050_write_regs(&mpu6050_dev,MPU_SAMPLE_RATE_REG,&data,1);   //设置数字低通滤波器
    return MPU_Set_LPF(rate/2); //自动设置LPF为采样率的一半
}

static uint8_t MPU_Write_Byte(uint8_t reg,uint8_t data)
{
    return mpu6050_write_regs(&mpu6050_dev,reg,&data,1);
}

static uint8_t MPU_Read_Byte(uint8_t reg)
{
    uint8_t read_data;
    mpu6050_read_regs(&mpu6050_dev,reg,&read_data,1);
    return read_data;
}

//初始化
uint8_t MPU_Init(void)
{
    uint8_t res=0;
    MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X80);//复位MPU6050
    mdelay(100);  //延时100ms
    MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X00);//唤醒MPU0650
    MPU_Set_Gyro_Fsr(3);                                //陀螺仪传感器,±2000dps
    MPU_Set_Accel_Fsr(0);                               //加速度传感器,±2g
    MPU_Set_Rate(200);                                  //设置采样率200Hz
    MPU_Write_Byte(MPU_INT_EN_REG,0X00);   //数据中断
    MPU_Write_Byte(MPU_USER_CTRL_REG,0X00);//I2C主模式关闭
    MPU_Write_Byte(MPU_FIFO_EN_REG,0X00);   //关闭FIFO
    MPU_Write_Byte(MPU_INTBP_CFG_REG,0X00);//选择中断电平,逻辑电平为0  50us高   查询立即自动清除
    res=MPU_Read_Byte(MPU_DEVICE_ID_REG);  //读取MPU6500的ID
    if(res==0X68) //器件ID正确
    {
        MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X01);     //设置CLKSEL,PLL X轴为参考
        MPU_Write_Byte(MPU_PWR_MGMT2_REG,0X00);     //加速度与陀螺仪都工作
        MPU_Set_Rate(200);                              //设置采样率为200Hz
    } else return 1;
    return 0;
}


void mpu6050_read_data(struct mpu6050_dev_t *dev)
{
    uint8_t buf[6];

    mpu6050_read_regs(dev,MPU_GYRO_XOUTH_REG,buf,6);

    dev->gyro[0]=(((uint16_t)buf[0]<<8)|buf[1]);
    dev->gyro[1]=(((uint16_t)buf[2]<<8)|buf[3]);
    dev->gyro[2]=(((uint16_t)buf[4]<<8)|buf[5]);

    mpu6050_read_regs(dev,MPU_ACCEL_XOUTH_REG,buf,6);

    dev->acc[0]=(((uint16_t)buf[0]<<8)|buf[1]);
    dev->acc[1]=(((uint16_t)buf[2]<<8)|buf[3]);
    dev->acc[2]=(((uint16_t)buf[4]<<8)|buf[5]);
}

发现一个问题,我在使用float的时候交叉编译器说不支持,不知道为什么,后面解决

应用测试程序

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>

/*
 * @description     : main主程序
 * @param - argc    : argv数组元素个数
 * @param - argv    : 具体参数
 * @return          : 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
    int fd;
    char *filename;
    int16_t databuf[7];
    int16_t gyro_x_adc, gyro_y_adc, gyro_z_adc;
    int16_t accel_x_adc, accel_y_adc, accel_z_adc;
    int16_t temp_adc;

    int ret = 0;

    if (argc != 2) {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0) {
        printf("can't open file %s\r\n", filename);
        return -1;
    }

    while (1) {
        ret = read(fd, databuf, sizeof(databuf));
        if(ret == 0) {          /* 数据读取成功 */
            accel_x_adc = databuf[0];
            accel_y_adc = databuf[1];
            accel_z_adc = databuf[2];
            gyro_x_adc = databuf[3];
            gyro_y_adc = databuf[4];
            gyro_z_adc = databuf[5];
            temp_adc = databuf[6];

            // These are the raw numbers from the chip, so will need tweaking to be really useful.
            // See the datasheet for more information
            printf("Acc. X = %d, Y = %d, Z = %d\n", accel_x_adc, accel_y_adc, accel_z_adc);
            printf("Gyro. X = %d, Y = %d, Z = %d\n", gyro_x_adc, gyro_y_adc, gyro_z_adc);
            // Temperature is simple so use the datasheet calculation to get deg C.
            // Note this is chip temperature.
            printf("Temp. = %f\n", (temp_adc / 340.0) + 36.53);
        }
        usleep(1000000); /*1000ms */
    }
    close(fd);  /* 关闭文件 */  
    return 0;
}