应用程序和驱动中使用I2C的流程

来源:互联网 发布:时差7小时 知乎 编辑:程序博客网 时间:2024/06/13 07:03

应用程序和驱动中使用I2C的流程

编写I2C设备驱动有两种方法。一种是利用系统给我们提供的i2c-dev.c来实现一个i2c适配器的设备文件。然后通过在应用层操作i2c适配器来控制i2c设备。另一种是为i2c设备,独立编写一个设备驱动。注意:在后一种情况下,是不需要使用i2c-dev.c的。

前一种方法也就是说只要系统实现了I2C适配器的驱动并生成了设备文件, 那么挂在其上面的I2C设备也可以在应用层直接通过操作这个设备文件来间接控制这个I2C设备, 这样就省去了专门为这个设备编写驱动的麻烦。本文也详细描述在应用层和驱动层分别控制I2C设备的流程。

一应用层操作I2C设备

即利用i2c-dev.c操作适配器,进而控制i2c设备, i2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read()、write()和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的i2c设备的存储空间或寄存器,并控制I2C设备的工作方式。

需要特别注意的是:i2c-dev.c的read()、write()方法都只适合于如下方式的数据格式(可查看内核相关源码):

 

所以不具有太强的通用性,如下面这种情况就不适用(通常出现在读目标时)。

 

而且read()、write()方法只适用用于适配器支持i2c算法的情况,如:

static const struct i2c_algorithms3c24xx_i2c_algorithm = {

            .master_xfer = s3c24xx_i2c_xfer,

            .functionality = s3c24xx_i2c_func,

        };

 

而不适合适配器只支持smbus算法的情况,如:

        static const struct i2c_algorithmsmbus_algorithm = {

            .smbus_xfer = i801_access,

            .functionality = i801_func,

        };

 

基于上面几个原因,所以一般都不会使用i2c-dev.c的read()、write()方法。最常用的是ioctl()方法。ioctl()方法可以实现上面所有的情况(两种数据格式、以及I2C算法和smbus算法)。

针对i2c的算法,需要熟悉struct i2c_rdwr_ioctl_data 、struct i2c_msg。使用的命令是I2C_RDWR。

        struct i2c_rdwr_ioctl_data {

            struct i2c_msg __user *msgs; /*pointers to i2c_msgs */

            __u32 nmsgs; /* number of i2c_msgs*/

        };

        struct i2c_msg {

            _ _u16 addr; /* slave address */

            _ _u16 flags; /* 标志(读、写) */

            _ _u16 len; /* msg length */

            _ _u8 *buf; /* pointer to msg data*/

        };

 

针对smbus算法,需要熟悉struct i2c_smbus_ioctl_data。使用的命令是I2C_SMBUS。对于smbus算法,不需要考虑“多开始信号时序”问题。

        struct i2c_smbus_ioctl_data {

            __u8 read_write; //读、写

            __u8 command; //命令

            __u32 size; //数据长度标识

            union i2c_smbus_data __user *data;//数据

        };

 

下面以本人工作中的一个实例讲解操作的具体过程。通过imx6q操作tlv320aic3204(一颗音频codec)。实现对tlv320aic3204的初始化工作。

首先在内核中已经包含了对imx6q中的i2c控制器驱动的支持。提供了i2c算法(非smbus类型的,所以后面的ioctl的命令是I2C_RDWR)

 


我们所用的I2C适配器的设备文件为:/dev/i2c-0, 接下来是使用流程:

1.      打开适配器文件, 并做相应的设置:

         /*iccard device*/

fd_codec= open(TLV320AIC3204_FILE, O_RDWR);  /*打开适配器文件*/

if(fd_codec< 0) {

           LOGE("can'topen codec device file");

           return0;

}

ioctl(fd_codec,I2C_SLAVE, TLV320AIC3204_I2C_ADDR);  /*配置slave地址*/

ioctl(fd_codec,I2C_TIMEOUT, 10);  /*配置超时时间*/

ioctl(fd_codec,I2C_RETRIES, 2);    /*配置重试次数*/

 

/*initializei2c_data*/ /*初始化I2C通信的核心数据结构*/

i2c_data.nmsgs= 2;    /*max 2 message*/

 

i2c_data.msgs= malloc(i2c_data.nmsgs * sizeof(struct i2c_msg));

if(i2c_data.msgs== NULL) {

           LOGE("cant'tmalloc memory");

           gotofailed1;

}

 

msgs[0]= i2c_data.msgs;

msgs[1]= i2c_data.msgs + 1;

 

msgs[0]->buf= malloc(2);  /*根据要传输的字节数来分配*/

if(!msgs[0]->buf){

           LOGE("can'tmalloc memory");

           gotofailed2;

}

 

msgs[1]->buf= malloc(2);  /*根据要传输的字节数来分配*/

if(!msgs[1]->buf){

           LOGE("can'tmalloc memory");

           gotofailed3;

}

        

2.      写寄存器流程如下:

structi2c_msg *p_msg = i2c_data.msgs;

if(fd_codec< 0)

           return-1;

/*justset i2c_data*/

i2c_data.nmsgs= 1;  /*写的话只要一个msg即可*/

 

p_msg->len= 2;

p_msg->addr= TLV320AIC3204_I2C_ADDR;

p_msg->flags= 0;     /*write*/

p_msg->buf[0]= reg;   /*register*/

p_msg->buf[1]= val;   /*data*/

ret =ioctl(fd_codec, I2C_RDWR, (unsigned long) &i2c_data);  /*发送命令给适配器*/

if(ret< 0) {

           LOGE("ioctlfailed! :%d", ret);

}

 

3.      读寄存器的流程:

int ret= 0;

structi2c_msg *msgs[2]; /*读需要两个msg, 1个用来发读register的地址, 一个读数据*/

 

if(fd_codec< 0)

           return-1;

 

msgs[0]= i2c_data.msgs;

msgs[1]= i2c_data.msgs + 1;

/*justset i2c_data*/

i2c_data.nmsgs= 2;

 

/*指定要读的寄存器地址的msg*/

msgs[0]->len= 1;

msgs[0]->addr= TLV320AIC3204_I2C_ADDR;

msgs[0]->flags= 0;   /*write*/

msgs[0]->buf[0]= reg;   /*register*/

 

/*读数据*/

msgs[1]->len= 1;

msgs[1]->addr= TLV320AIC3204_I2C_ADDR;

msgs[1]->flags= 1;   /*read*/

msgs[1]->buf[0]= 0;

 

ret =ioctl(fd_codec, I2C_RDWR, (unsigned long) &i2c_data);  /*发送请求命令*/

if(ret< 0) {

           LOGE("ioctlfailed! :%d", ret);

 

           returnret;

}

else

           return msgs[1]->buf[0];

 

以上讲述了一种比较常用的利用i2c-dev.c操作i2c设备的方法,这种方法可以说是在应用层完成了对具体i2c设备的驱动工作。

 

二驱动层操作I2C设备

即为I2C设备编写驱动程序, 首先要确保I2C适配器的驱动都正常, 因为我们需要它来实现正真的I2C传输, 下面将以本人项目中的一颗RTC的I2C设备:pcf8563为例来讲解大体的I2C驱动流程。

1.      定义一个i2c_board_info的数据块:

static structi2c_board_info mxc_i2c1_board_info[] __initdata = {

{

           I2C_BOARD_INFO("pcf8563", 0x51), /*名字要跟驱动里的同名, 0x51为I2C的设备地址*/

},

};

2.      注册到系统中去:

i2c_register_board_info(1,mxc_i2c1_board_info,

                                       ARRAY_SIZE(mxc_i2c1_board_info));

3.      编写 PCF8563的I2C驱动, 这个驱动在内核里已经实现了, 在driver/rtc/rtc-pcf8563.c, 我们将描述这个驱动的框架, 及怎么跟上面注册的i2c设备绑定在一起。

3.1      I2C驱动都有一个i2c_driver的类型, 这里是:

static struct i2c_driver pcf8563_driver = {

        .driver                = {

                 .name       = "rtc-pcf8563",

        },

        .probe                = pcf8563_probe,

        .remove            = pcf8563_remove,

        .id_table  = pcf8563_id,

};

pcf8563_id 内容如下:

static const struct i2c_device_idpcf8563_id[] = {

        {"pcf8563", 0 },   /*可以看到名称和上面的设备名称相同*/

        {"rtc8564", 0 },

        {}

};

3.2      把i2c_driver注册到系统中去:

static int __init pcf8563_init(void)

{

        returni2c_add_driver(&pcf8563_driver);

}

注册进去后, I2C总线会去查找哪些已经挂在总线上, 但还没有找到驱动的设备, 然后,根据设备名称和驱动支持的id列表中去匹配, 如果匹配的话, 就调用驱动的probe函数, 显然,这边我们找到了pcf8563的设备,pcf8563_probe会被调用。

3.3      pcf8563_probe函数会去向rtc子系统注册一个RTC设备, 这边不详细解说了, 我们着重看看在这个I2C驱动里是如何跟I2C设备通信的,

3.4      读写设备:

读设备如下:

static int pcf8563_get_datetime(structi2c_client *client, struct rtc_time *tm)

{

         structpcf8563 *pcf8563 = i2c_get_clientdata(client);

         unsignedchar buf[13] = { PCF8563_REG_ST1 };

 

         structi2c_msg msgs[] = {

                   {client->addr, 0, 1, buf }, /* setupread ptr */

                   {client->addr, I2C_M_RD, 13, buf },        /*read status + date */

         };

 

         /*read registers */

         if((i2c_transfer(client->adapter, msgs, 2)) != 2) {

                   dev_err(&client->dev,"%s: read error\n", __func__);

                   return-EIO;

         }

    ….

}

 

写设备如下:

static int pcf8563_set_datetime(structi2c_client *client, struct rtc_time *tm)

{

         structpcf8563 *pcf8563 = i2c_get_clientdata(client);

         inti, err;

         unsignedchar buf[9];

     …….

         /*hours, minutes and seconds */

         buf[PCF8563_REG_SC]= bin2bcd(tm->tm_sec);

         buf[PCF8563_REG_MN]= bin2bcd(tm->tm_min);

         buf[PCF8563_REG_HR]= bin2bcd(tm->tm_hour);

 

         buf[PCF8563_REG_DM]= bin2bcd(tm->tm_mday);

 

         /*month, 1 - 12 */

         buf[PCF8563_REG_MO]= bin2bcd(tm->tm_mon + 1);

 

         /*year and century */

         buf[PCF8563_REG_YR]= bin2bcd(tm->tm_year % 100);

         if(pcf8563->c_polarity ? (tm->tm_year >= 100) : (tm->tm_year <100))

                   buf[PCF8563_REG_MO]|= PCF8563_MO_C;

 

         buf[PCF8563_REG_DW]= tm->tm_wday & 0x07;

 

         /*write register's data */

         for(i = 0; i < 7; i++) {

                   unsignedchar data[2] = { PCF8563_REG_SC + i,

                                                        buf[PCF8563_REG_SC+ i] };

 

                   err= i2c_master_send(client, data, sizeof(data));

                   if(err != sizeof(data)) {

                            dev_err(&client->dev,

                                     "%s:err=%d addr=%02x, data=%02x\n",

                                     __func__,err, data[0], data[1]);

                            return-EIO;

                   }

         };

 

         return0;

}

 

部分内容来自刘洪涛老师, 请参考:

http://blog.csdn.net/hongtao_liu/article/details/4964244

http://blog.csdn.net/hongtao_liu/article/details/5260739


0 0