i2c总线1

说明:
1. i2c是一种接口和soc链接一起。(遵从i2c协议 )
2. 不管什么协议的通信,都需要提供一条时钟线。如:i2c scl线

一、 i2c协议和时序(从设备一直在等待master的选址)

1. i2c是从设备的开发 
2. 每个从设备都有一个设备地址(7bit or 10bit),有的会有内部地址(寄存器也是地址)
3. 从设备硬件: 
    cmos camera
    ts     :电容屏摄像头
    gsensor:重力传感器
    eeprom :存储设备
    HDMI   :高清输出接口
3. 在scl时钟线为高时收集数据,低的数据无效

这里写图片描述

注:
1. 从设备地址后要跟着一个读写位,内部地址不用
2. 每发完一帧数据就有一个ack

二、i2c子系统软件框架
i2c 框架图

1. 对从设备统一的管理
2. 分层
    应用层:
-------------------------------------------------------------------
    i2c driver:从设备驱动层
         1 需要和应用层进行交互
         2 封包数据,不知道数据是如何写入到硬件
-------------------------------------------------------------------
        i2c 核心层:
        1 维护i2c 总线,包括i2c driver, i2c client链表
        2 文件位置:drivers/i2c/i2c-core.c
-------------------------------------------------------------------
    i2c adapter层:i2c控制层,初始化i2c控制器
        1 完成将数据写入或读取-从设备硬件
        2 不知道数据具体是什么,但是知道如何操作从设备
                3 文件位置:drivers/i2c/busses/i2c-s3c2410.c
        ||
        \/
        从设备(硬件)

3 确保i2c core和i2c adatper层必须编译进内核:

    make menuconfig
        Device Drivers  --->
            -*- I2C support  ---> //编译i2c-core.c
                I2C Hardware Bus support  --->
                    <*> S3C2410 I2C Driver // i2c-s3c2410.c

4. 板子跑起来后
    /sys/bus/i2c/
    /sys/bus/i2c/devices/i2c-0 
    [root@farsight i2c-0]# cat name
    s3c2410-i2c

5. 设备树的添加方法
    I2c子系统中涉及到的设备树文件:
    1. i2c控制器地址
        0x1386_0000, 
        0x1387_0000,
        0x1388_0000, 
        0x1389_0000,
        0x138A_0000,
        0x138B_0000, ------ MPU6050
        0x138C_0000, 
        0x138D_0000, 
        0x138E_0000
    2. MPU6050: 从设备地址是0x68
        soc GPB_3 ---  I2C_SCL5
        GPB_3 ---  I2C_SDA5
        GPX3_3--- GYRO_INT
    在内核中默认就有了i2c0--13860000.i2c

    模板:
    3. 控制器对应的设备树:arch/arm/boot/dts/exynos4.dtsi
        i2c_0: i2c@13860000 {
                    #address-cells = <1>;
                    #size-cells = <0>;
                    compatible = "samsung,s3c2440-i2c";
                    reg = <0x13860000 0x100>;
                    interrupts = <0 58 0>;
                    clocks = <&clock 317>;
                    clock-names = "i2c";
                    pinctrl-names = "default";
                    pinctrl-0 = <&i2c0_bus>;
                    status = "disabled";
            };

        i2c_5: i2c@138B0000 {
                    #address-cells = <1>;
                    #size-cells = <0>;
                    compatible = "samsung,s3c2440-i2c";
                    reg = <0x138B0000 0x100>;
                    interrupts = <0 63 0>;
                    clocks = <&clock 322>;
                    clock-names = "i2c";
                    status = "disabled";
            };

    4. 描述从设备信息的设备树的模板
        arch/arm/boot/dts/exynos4412-fs4412.dts
        i2c@13860000 {
                    #address-cells = <1>;
                    #size-cells = <0>;
                    samsung,i2c-sda-delay = <100>;
                    samsung,i2c-max-bus-freq = <20000>;
                    pinctrl-0 = <&i2c0_bus>;
                    pinctrl-names = "default";
                    status = "okay";

                    s5m8767_pmic@66 {
                            compatible = "samsung,s5m8767-pmic";
                            reg = <0x66>;
            }

        }

    5. 新增加i2c从设备,arch/arm/boot/dts/exynos4412-fs4412.dts增加i2c5控制和它包含了设备设备
            i2c@138B0000 {/*i2c adapter5信息*/
                    #address-cells = <1>;
                    #size-cells = <0>;
                    samsung,i2c-sda-delay = <100>;
                    samsung,i2c-max-bus-freq = <20000>;
                    pinctrl-0 = <&i2c5_bus>;
                    pinctrl-names = "default";
                    status = "okay";

                    mpu6050@68 { /*i2c client信息*/
                            compatible = "invensense,mpu6050";
                            reg = <0x68>;
            };
            };

    6. 保存后make dtbs
        cp -raf arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot/

三、mpu6050陀螺仪驱动编程(变量名 of_xxx 开头的都和设备树有关)

1. 编程方式不考虑具体的硬件
2. 几个重要的结构体
    1. 表示是一个从设备的驱动对象
        struct i2c_driver {
            int (*probe)(struct i2c_client *, const struct i2c_device_id *);
            int (*remove)(struct i2c_client *);
            struct device_driver driver;                     //继承了父类
                    |
                const struct of_device_id   *of_match_table;
            const struct i2c_device_id *id_table;            //用于做比对,非设备树的情况
        }
        注册和注销:
        int i2c_add_driver( struct i2c_driver *driver);
        void i2c_del_driver(struct i2c_driver *);

    2. 描述一个从设备的信息,不需要在代码中创建,因为是由i2c adapter帮我们创建
        struct i2c_client {
            unsigned short addr;           //从设备地址,来自于设备树中<reg>
            char name[I2C_NAME_SIZE];      //用于和i2c driver进行匹配,来自于设备树中compatible
            struct i2c_adapter *adapter;   //指向当前从设备所存在的i2c adapter
            struct device dev;             // 继承了父类
        }
        创建i2c client的函数
        struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)

    3. 描述一个i2c控制器,也不是我们要构建,原厂的代码会帮我们构建
        struct i2c_adapter {
            const struct i2c_algorithm *algo; //算法
                    |
                int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);
            struct device dev; //继承了父类,也会被加入到i2c bus
            int nr; //编号

        }
        注册和注销:
        int i2c_add_adapter(struct i2c_adapter * adapter);
        void i2c_del_adapter(struct i2c_adapter * adap);

    4. 描述一个从设备要发送的数据的数据包
        struct i2c_msg {
            __u16 addr; //从设备地址,发送给那个从设备
            __u16 flags;    //读1还是写0
            __u16 len;  //发送数据的长度
            __u8 *buf;  //指向数据的指针
        };
        1. 写从设备
            int i2c_master_send(const struct i2c_client * client,const char * buf,int count)
        2. 读从设备     
            int i2c_master_recv(const struct i2c_client * client,char * buf,int count)
        3. 以上两个函数都调用了:
            int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

    5. ioctl: 给驱动发送不同指令
        应用程序:
            ioctl(fd, cmd, args);
        ========================================
        驱动中:xxx_ioctl()
        {
            switch(cmd){
            }
        }

注:重要的函数

1. 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)
2. 以上两个函数都调用了:
   int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

=====================================================================
四、 mpu6050陀螺仪示例

应用层

1. 头文件:
    #ifndef __MPU6050_H__
    #define __MPU6050_H__

    union mpu6050_data{
        struct{ 
            short x;
            short y;
            short z;
        }accel;
        struct{ 
            short x;
            short y;
            short z;
        }gyro;
        short temp;
    };

    #define IOC_GET_ACCEL  _IOR('M', 0x34,union mpu6050_data)
    #define IOC_GET_GYRO  _IOR('M', 0x35,union mpu6050_data)
    #define IOC_GET_TEMP  _IOR('M', 0x36,union mpu6050_data)
    #endif
2.test code
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/ioctl.h>


    #include "mpu6050.h"


    int main(int argc, char *argv[])
    {
        int fd;
        union mpu6050_data data;
        fd = open("/dev/mpu_sensor", O_RDWR);
        if(fd < 0)
        {
            perror("open");
            exit(1);
        }
        while(1)
        {
            ioctl(fd, IOC_GET_ACCEL, &data);
            printf("accel data :  x = %d, y=%d, z=%d\n", data.accel.x, data.accel.y, data.accel.z);
            ioctl(fd, IOC_GET_GYRO, &data);
            printf("gyro data :  x = %d, y=%d, z=%d\n", data.gyro.x, data.gyro.y, data.gyro.z);
            sleep(1);
        }
        close(fd);
        return 0;
    }

3. 驱动层
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/input.h>
    #include <linux/interrupt.h>
    #include <linux/slab.h>
    #include <linux/of.h>
    #include <linux/of_irq.h>
    #include <linux/of_gpio.h>
    #include <linux/i2c.h>

    #include <asm/io.h>
    #include <asm/uaccess.h>

    #include "mpu6050.h"

    #define SMPLRT_DIV      0x19    //采样频率寄存器-25 典型值:0x07(125Hz)
                        //寄存器集合里的数据根据采样频率更新
    #define CONFIG          0x1A    //配置寄存器-26-典型值:0x06(5Hz)
                        //DLPF is disabled(DLPF_CFG=0 or 7)
    #define GYRO_CONFIG     0x1B    //陀螺仪配置-27,可以配置自检和满量程范围
                        //典型值:0x18(不自检,2000deg/s)
    #define ACCEL_CONFIG        0x1C    //加速度配置-28 可以配置自检和满量程范围及高通滤波频率
                        //典型值:0x01(不自检,2G,5Hz)
    #define ACCEL_XOUT_H    0x3B //59-65,加速度计测量值 XOUT_H
    #define ACCEL_XOUT_L    0x3C  // XOUT_L
    #define ACCEL_YOUT_H    0x3D  //YOUT_H
    #define ACCEL_YOUT_L    0x3E  //YOUT_L
    #define ACCEL_ZOUT_H    0x3F  //ZOUT_H
    #define ACCEL_ZOUT_L    0x40 //ZOUT_L---64
    #define TEMP_OUT_H      0x41 //温度测量值--65
    #define TEMP_OUT_L      0x42
    #define GYRO_XOUT_H     0x43 //陀螺仪值--67,采样频率(由寄存器 25 定义)写入到这些寄存器
    #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 //陀螺仪值--72
    #define PWR_MGMT_1      0x6B //电源管理 典型值:0x00(正常启用)

    //设计一个全局的设备对象
    struct mpu_sensor{
        int dev_major;
        struct device *dev;
        struct class *cls;
        struct i2c_client *client;//记录probe中client
    };

    struct mpu_sensor *mpu_dev;


    int mpu6050_write_bytes(struct i2c_client *client, char *buf, int count)
    {

        int ret;
        struct i2c_adapter *adapter = client->adapter;
        struct i2c_msg msg;

        msg.addr = client->addr;
        msg.flags = 0;
        msg.len = count;
        msg.buf = buf;


        ret = i2c_transfer(adapter, &msg,  1);

        return ret==1?count:ret;

    }

    int mpu6050_read_bytes(struct i2c_client *client, char *buf, int count)
    {

        int ret;
        struct i2c_adapter *adapter = client->adapter;
        struct i2c_msg msg;

        msg.addr = client->addr;
        msg.flags = I2C_M_RD;
        msg.len = count;
        msg.buf = buf;

        ret = i2c_transfer(adapter, &msg,  1);

        return ret==1?count:ret;
    }

    //读取某个特定寄存器的地址,然后返回值
    int mpu6050_read_reg_byte(struct i2c_client *client, char reg)
    {
        // 先写寄存器的地址, 然后在读寄存器的值

        int ret;
        struct i2c_adapter *adapter = client->adapter;
        struct i2c_msg msg[2];

        char rxbuf[1];

        msg[0].addr = client->addr;
        msg[0].flags = 0;
        msg[0].len = 1;
        msg[0].buf = &reg;

        msg[1].addr = client->addr;
        msg[1].flags = I2C_M_RD;
        msg[1].len = 1;
        msg[1].buf = rxbuf;

        ret = i2c_transfer(adapter, msg,  2);
        if(ret < 0)
        {
            printk("i2c_transfer read error\n");
            return ret;
        }
        return rxbuf[0];
    }
    int mpu6050_drv_open(struct inode *inode, struct file *filp)
    {
        return 0;
    }
    int mpu6050_drv_close(struct inode *inode, struct file *filp)
    {
        return 0;
    }
    long mpu6050_drv_ioctl (struct file *filp, unsigned int cmd, unsigned long args)
    {
        union mpu6050_data data;
        switch(cmd){
            case IOC_GET_ACCEL:
                //读数据
                data.accel.x = mpu6050_read_reg_byte(mpu_dev->client, ACCEL_XOUT_L);
                data.accel.x |= mpu6050_read_reg_byte(mpu_dev->client, ACCEL_XOUT_H) << 8;

                data.accel.y = mpu6050_read_reg_byte(mpu_dev->client, ACCEL_YOUT_L);
                data.accel.y |= mpu6050_read_reg_byte(mpu_dev->client, ACCEL_YOUT_H) << 8;

                data.accel.z = mpu6050_read_reg_byte(mpu_dev->client, ACCEL_ZOUT_L);
                data.accel.z |= mpu6050_read_reg_byte(mpu_dev->client, ACCEL_ZOUT_H) << 8;
                break;
            case IOC_GET_GYRO:
                data.gyro.x = mpu6050_read_reg_byte(mpu_dev->client, GYRO_XOUT_L);
                data.gyro.x |= mpu6050_read_reg_byte(mpu_dev->client, GYRO_XOUT_H) << 8;


                data.gyro.y = mpu6050_read_reg_byte(mpu_dev->client, GYRO_YOUT_L);
                data.gyro.y |= mpu6050_read_reg_byte(mpu_dev->client, GYRO_YOUT_H) << 8;

                data.gyro.z= mpu6050_read_reg_byte(mpu_dev->client, GYRO_ZOUT_L);
                data.gyro.z |= mpu6050_read_reg_byte(mpu_dev->client, GYRO_ZOUT_H) << 8;
                break;
            case IOC_GET_TEMP:
                data.temp = mpu6050_read_reg_byte(mpu_dev->client, TEMP_OUT_L);
                data.temp |= mpu6050_read_reg_byte(mpu_dev->client, TEMP_OUT_H) << 8;
                break;
            default:
                printk("invalid cmd\n");
                return -EINVAL;
        }   
        //将所有的数据交给用户
        if(copy_to_user((void __user * )args, &data, sizeof(data)) > 0)
            return -EFAULT; 
        return 0;
    }   
    const struct file_operations mpu6050_fops = {
        .open = mpu6050_drv_open,
        .release = mpu6050_drv_close,
        .unlocked_ioctl = mpu6050_drv_ioctl,

    };

    int mpu6050_drv_probe(struct i2c_client *client, const struct i2c_device_id *id)
    {
        printk("-----%s----\n", __FUNCTION__);

        /*
            申请设备号,实现fops
            创建设备文件
            通过i2c的接口去初始化i2c从设备
        */  
        mpu_dev = kzalloc(sizeof(struct mpu_sensor), GFP_KERNEL);       
        mpu_dev->client = client;   
        mpu_dev->dev_major = register_chrdev(0,"mpu_drv", &mpu6050_fops);   
        mpu_dev->cls = class_create(THIS_MODULE, "mpu_cls");    
        mpu_dev->dev = device_create(mpu_dev->cls, NULL, MKDEV(mpu_dev->dev_major, 0),
                    NULL, "mpu_sensor");

        char buf1[2] = {PWR_MGMT_1, 0x0};
        mpu6050_write_bytes(mpu_dev->client, buf1, 2);

        char buf2[2] = {SMPLRT_DIV, 0x07};
        mpu6050_write_bytes(mpu_dev->client, buf2, 2);

        char buf3[2] = {CONFIG, 0x06};
        mpu6050_write_bytes(mpu_dev->client, buf3, 2);

        char buf4[2] ={GYRO_CONFIG, 0x18};
        mpu6050_write_bytes(mpu_dev->client, buf4, 2);

        char buf5[2] = {ACCEL_CONFIG, 0x01};
        mpu6050_write_bytes(mpu_dev->client, buf5, 2);

        return 0;
    }   
    int mpu5060_drv_remove(struct i2c_client *client)
    {
        printk("-----%s----\n", __FUNCTION__);
        device_destroy(mpu_dev->cls, MKDEV(mpu_dev->dev_major, 0));
        class_destroy(mpu_dev->cls);
        unregister_chrdev(mpu_dev->dev_major, "mpu_drv");
        kfree(mpu_dev);
        return 0;
    }   
    const struct of_device_id  of_mpu6050_id[] = {
            {
                .compatible = "invensense,mpu6050",
            },
            {/*northing to be done*/},

    };  
    const struct i2c_device_id mpu_id_table[] = {
            {"mpu6050_drv", 0x1111},
            {/*northing to be done*/},
    };

    struct i2c_driver mpu6050_drv = {
        .probe = mpu6050_drv_probe,
        .remove = mpu5060_drv_remove,
        .driver = {
            .name = "mpu6050_drv",//随便写,/sys/bus/i2c/driver/mpu6050_drv
            .of_match_table = of_match_ptr(of_mpu6050_id),
        },      
        .id_table = mpu_id_table,//非设备树情况下的匹配,在设备树的模式下不需要使用 
    };  

    static int __init mpu6050_drv_init(void)
    {
        // 1,构建i2c driver,并注册到i2c总线
        return i2c_add_driver(&mpu6050_drv);    
    }   
    static void __exit mpu6050_drv_exit(void)
    {
        i2c_del_driver(&mpu6050_drv);   
    }   
    module_init(mpu6050_drv_init);
    module_exit(mpu6050_drv_exit);
    MODULE_LICENSE("GPL");  
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32F103ZE I2C1调试手记近日因项目需要,使用100PIN的STM32带FSMC功能驱动3.2"TFTLCD;在使用EEPROM时发现原来的I2C程序居然无法使用,郁闷了! 先说一下我这个I2C的驱动程序是经过STM32的官方库函数整理而成,API支持全系列EEPROM以及8BIT地址的其它I2C设备,一直用的很爽。 赶紧查找问题原因,发现连STA信号都无法启动完成,一直循环在等待状态while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); 真是见鬼了,居然连STA信号都无法建立,首先怀疑是硬件问题,于是赶紧查找硬件问题,换EEPROM,无果。奇了怪了,其它项目用的好好的程序,问百度吧?百度一下,这个问题还真多啊,而且还一模一样,问题都没结果,基本的认识是说ST芯片的问题,只能用模拟总线解决。 难道真是芯片问题?这可能吗?这可是ST啊,放眼全球有几家公司能与之抗衡啊。我首先觉得不可能,理由是为什么我其它项目用的好好的,虽说不是同PIN的芯片,可是同系列啊。于是我用其它项目的程序稍做修改,只开放IIC功能把程序下载到103ZE,运行一切正常。这就验证了我的判断,芯片不可能有问题。可为什么不行呢?难道是底层库函数的问题,于是升级库函数,移植到3.0版本的,这样捣鼓一下,一上午时间就这样没了,郁闷啊郁闷到差点绝望啊!!!先吃完中午饭,准备下午再战。再问百度吧,找来找去还是没结果,都说是芯片问题。纠结,难不成真的要用模拟I2C总线解决。不甘心啊,土法炼钢吧,把所有初始化程序一个一个注释掉,下载测试,当注释掉LCD初始化程序时,奇迹出现了,I2C总线OK了,天啦难道是LCD造成的?查看LCD初始化程序,FSMC初始化程序,没发现问题啊,再加上LCD初始化程序,I2C又不能工作了,看来确实是LCD的功能造成,先把FSMC注释掉,运行I2C又OK了,终于找到你了,就是FSMC造成的,看程序没有任何错误,还是查手册吧,在手册中看到I2C1的SDA脚与FSMC_NADV脚是共用一个端口,难道是这个引起的,查看手册《STM32F101xC/D/E 和 STM32F103xC/D/E勘误表》终于发现问题的根源。原文描述:2.7.15 FSMC和 I2C1以及TIM4_CH2问题描述如果要使用FSMC功能,NADV被配置成复用输出,该信号会被默认地置位。这样就和TIM4_CH2和I2C1的SDA信号发生冲突。暂时解决办法当使用FSMC的同时,不要使用TIM4_CH2。如果要使用I2C1并且封装允许,就把该功能重映射到PB8/PB9。2.7.13 SDIO 和经过重映射的I2C1配置条件当SDIO配置成1位或者4位模式,会和被重映射到PB8/9引脚上的I2C1发生冲突。冲突发生在以下信号之间:● I2C1_SCL和SDIO_D4● I2C1_SDA和SDIO_D5暂时解决办法当使用SDIO的同时,不要重映射I2C1的功能到PB8/PB9。问题找到了,就是这个原因啊,天杀的ST,映射功能只能到PB8 PB9端口上,不能映射到任意商品上,可我的PB8 PB9端口被SDIO功能占用了。I2C2端口也被USART3功能占用了。问题是找到了,可最后结果只能模拟总线来实现了。调试结论:1. ST的芯片不会有问题。ST官方的库函数是非常好用的,用的很爽的!2. 当使用大容量芯片具有FSMC功能与I2C1功能同时使用时, 需要把I2C1映射到PB9 PB9端口上去。3. 如果要同时使用FSMC I2C1 SDIO功能时,I2C1只能用模拟总线方式实现。因为SDIO功能会占用PB8 PB9端口。以下是I2C模拟总线程序源代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值