zoukankan      html  css  js  c++  java
  • linuxok6410的I2C驱动分析---用户态驱动

    3  i2c-dev

    3.1 概述

    之前在介绍I2C子系统时,提到过使用i2c-dev.c文件在应用程序中实现我们的I2C从设备驱动。不过,它实现的是一个虚拟,临时的i2c_client,随着设备文件的打开而产生,并随着设备文件的关闭而撤销。I2c-dev.c针对每个I2C适配器生成一个主设备号为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口,所以i2c-dev.c的主题是”i2c_driver成员函数+字符设备驱动”。

    3.2 i2c-dev.c源码分析

    初始化模块

    1. static int __init i2c_dev_init(void)  
    2. {  
    3.          res= register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);  
    4.    
    5.          i2c_dev_class= class_create(THIS_MODULE, "i2c-dev");  
    6.    
    7.          /*Keep track of adapters which will be added or removed later */  
    8.          res= bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);  
    9.    
    10.          /*绑定已经存在的适配器 */  
    11.          i2c_for_each_dev(NULL,i2cdev_attach_adapter);  
    12. }  

    I2c-dev初始化函数主要做了注册名为”i2c”的字符设备文件和”i2c-dev”的类

    i2cdev_read和i2cdev_write

    I2c-dev.c中实现的i2cdev_read和i2cdev_write函数不具有太强的通用性,只适合下面这种单开始信号情况:

    而不适合多开始信号的情况:

    所以我们经常会使用i2cdev_ioctl函数的I2C_RDWR,在分析i2cdev_ioctl函数之前,我们需要了解一个结构体:

    1. /* This is the structure as used in theI2C_RDWR ioctl call */  
    2. struct i2c_rdwr_ioctl_data {  
    3.          structi2c_msg __user *msgs;         /* pointersto i2c_msgs */  
    4.          __u32nmsgs;                    /* number ofi2c_msgs */  
    5. };  

    Msgs     表示单个开始信号传递的数据;

    Nmsgs     表示有多少个msgs,比如上图,单开始信号时,nmsgs等于1;多开始信号时,nmsgs等于2

    1. struct i2c_msg {  
    2.          __u16addr;     /* slave address                         */  
    3.          __u16flags;  /* 默认为写入 */  
    4. #define I2C_M_TEN                  0x0010     /*this is a ten bit chip address */  
    5. #define I2C_M_RD           0x0001     /* read data,from slave to master */  
    6. #define I2C_M_NOSTART                  0x4000     /* if I2C_FUNC_PROTOCOL_MANGLING */  
    7. #define I2C_M_REV_DIR_ADDR     0x2000     /*if I2C_FUNC_PROTOCOL_MANGLING */  
    8. #define I2C_M_IGNORE_NAK          0x1000     /*if I2C_FUNC_PROTOCOL_MANGLING */  
    9. #define I2C_M_NO_RD_ACK           0x0800     /* if I2C_FUNC_PROTOCOL_MANGLING */  
    10. #define I2C_M_RECV_LEN               0x0400     /* length will be first received byte */  
    11.          __u16len;                  /* msg length                              */  
    12.          __u8*buf;                 /* pointer to msgdata                       */  
    13. };  

    3.3 eeprom实例

    预备知识

    使用的ok6410开发板,eeprom的地址为0x50,实验完成一个数据的读写,先看下读写时序

    AT24C02任意地址字节写的时序:

    AT24C02任意地址字节写的时序:

    用户态驱动:

    #include <stdio.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/ioctl.h>
    #include <stdlib.h>
    //#include <linux/types.h>

    #define I2C_RDWR 0x0707

    struct i2c_msg {
    unsigned short addr; /* slave address */
    unsigned short flags;
    unsigned short len; /* msg length */
    unsigned char *buf; /* pointer to msg data */
    };

    struct i2c_rdwr_ioctl_data {
    struct i2c_msg *msgs; /* pointers to i2c_msgs */
    unsigned int nmsgs; /* number of i2c_msgs */
    };

    int main()
    {
    int fd;
    struct i2c_rdwr_ioctl_data e2prom_data;

    //1. 打开通用设备文件
    fd = open("/dev/i2c-0", O_RDWR);

    //为i2c_rdwr_ioctl_data中的struct i2c_msg *分配空间
    e2prom_data.msgs = (struct i2c_msg *)malloc(2*sizeof(struct i2c_msg)); // 构造两条消息

    //2. 构造写数据到eeprom
    e2prom_data.nmsgs = 1; // 只有一条消息
    (e2prom_data.msgs[0]).len = 2; //长度等于2,第一个字节代表的是i2c设备的内部地址,第二个字节代表的是写入的数据
    (e2prom_data.msgs[0]).addr = 0x50; // 从设备地址(e2prom的地址),注意这里是不带方向的!
    (e2prom_data.msgs[0]).flags = 0; // 方向由flag标志位来指明,0代表了写,1代表了读
    (e2prom_data.msgs[0]).buf = (unsigned char*)malloc(2); // 这里只分配两个字节(内部偏移地址一字节,数据1字节)
    (e2prom_data.msgs[0]).buf[0] = 0x10; // 数据将写入e2prom中的内部0x10地址中
    (e2prom_data.msgs[0]).buf[1] = 0x60; // 写入e2prom中内 部0x10地址中的数据位0x60

    //3. 使用ioctl写入数据
    ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data, I2C_RDWR); // 这里面的命令参数对应的是驱动内部的ioctl中的case语句中的参数

    //4. 构造从eeprom读数据的消息
    e2prom_data.nmsgs = 2; // 读数据需要两条消息
    (e2prom_data.msgs[0]).len = 1; //长度为一个字节,代表的是i2c设备的内部地址
    (e2prom_data.msgs[0]).addr = 0x50; // 从设备地址(e2prom的地址),注意这里是不带方向的!
    (e2prom_data.msgs[0]).flags = 0; // 方向由flag标志位来指明,0代表了写,1代表了读
    (e2prom_data.msgs[0]).buf[0] = 0x10; // 数据将写入e2prom中的内部0x10地址中
    // 第二条消息(读数据)
    (e2prom_data.msgs[1]).len = 1; //长度为一个字节,代表的是i2c设备的内部地址
    (e2prom_data.msgs[1]).addr = 0x50; // 从设备地址(e2prom的地址),注意这里是不带方向的!
    (e2prom_data.msgs[1]).flags = 1; // 方向由flag标志位来指明,0代表了写,1代表了读
    (e2prom_data.msgs[1]).buf = (unsigned char*)malloc(2);
    (e2prom_data.msgs[1]).buf[0] = 0; // 数据从e2prom中的内部0x10地址中读出

    //5. 使用ioctl读出数据
    ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);
    printf("buf[0] = %x ", (e2prom_data.msgs[1]).buf[0]);

    //6. 关闭设备
    close(fd);
    }

      

  • 相关阅读:
    VSCode 设置 CPP 代码风格
    KiCad EDA 5.1.2 使用圆形板框时出现无法走线的问题
    oracle的sql优化
    mybatis 自动生成xml文件配置
    sql循环遍历
    XML
    oracle的concat的用法
    oracle 按某个字段查询重复数据
    Xshell 4的上传与下载
    Oracle之锁
  • 原文地址:https://www.cnblogs.com/chd-zhangbo/p/5214657.html
Copyright © 2011-2022 走看看