i2c_msg淺析


i2c_msg淺析

在學習i2c設備驅動的時候,不經意間發現一個關於結構體i2c_msg的問題,查閱了兩天的資料,發現網上基本說的都差不多,當時不理解,以為別人說的不對,理解之后發現都是對的,只是當時不懂。為了防止有小伙伴和我一樣鑽牛角尖,白白耽誤時間,就大概說一下,當然了,我也是在學習的過程中,難免會有些地方說的不對。

i2c的讀寫時序講解

在講解之前,先大概介紹以下i2c的讀寫時序問題,當然了,都學到i2c了,時序肯定也懂了,我也就不細講了,主要是需要配合時序圖進行后面i2c_msg的講解。

i2c的讀操作

i2c的讀時序如圖所示,通過時序圖可以發現,i2c進行讀操作時可以概括為“兩大步”:寫和讀。概括來說也就是先告訴指定設備我要讀取的寄存器時哪個,再從這個寄存器中讀取數據。希望讀者可以盡量理解划分為兩步,這對於編寫Linux中的i2c設備驅動讀操作時很有幫助。

i2c的寫操作

i2c的寫時序如圖所示,通過時序圖可以發現,i2c在進行寫操作的時候只有一個方向:那就是直接向指定的寄存器中寫入數據,也就是只有一個方向,可以將其理解為只有“一步”寫操作。

i2c_msg結構體

i2c_msg結構體的成員變量如下所示,大概瀏覽即可,對於其中的某些定義不清楚的可以百度查閱。關鍵的地方在於,一個i2c_msg結構的變量,代表着一次單方向的傳輸,這一點很重要,也是我一開始迷糊的地方。

 struct i2c_msg {
  __u16 addr; /* 從機地址 */
  __u16 flags; /* 標志位,指定進行的操作 */
 #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
 #define I2C_M_RD 0x0001 /* read data, from slave to master */
 #define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
 #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
 #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
 #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
 #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
 #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
  __u16 len; /* msg length(單位為字節,需要注意) */
  __u8 *buf; /* pointer to msg data */
 };

i2c設備驅動讀函數編寫

通過上面對於讀時序的描述,將i2c的讀操作分為“兩步”,即寫方向和讀方向,因此可以有兩個i2c_msg結構的變量,分別通過對結構體成員變量flag的賦值來確定傳輸方向,函數編寫如下:

 /* 從ap3216c讀取多個寄存器數據
  * 入口參數@client:從機地址
  * @reg :寄存器地址
  * @buffer:保存讀取數據
  * @length:reg/buffer的長度
  */
 static int  ap3216c_read_regs(struct i2c_client *client,u8 reg, u8 *buffer, int length)
 {
     int err = 0;
 
     /* msg[0]是發送要讀取的寄存器首地址 */
     struct i2c_msg msg[] = {
        {
            .addr  = client->addr,
            .flags = 0,
            .len   = 1,     //表示寄存器地址字節長度,是以byte為單位
            .buf   = &reg,
        },
        {
            .addr  = client->addr,
            .flags = I2C_M_RD,
            .len   = length, //表示期望讀到數據的字節長度(寄存器長度),是以byte為單位
            .buf   = buffer, //將讀取到的數據保存在buffer中
        },
    };
 
     err = i2c_transfer(client->adapter, msg,2);
     if(err != 2)C
    {
         err = -EINVAL;
         printk("read regs from ap3216c has been failed\n\r");
    }
     
     return err;
 }

函數解析:由於讀操作分為了寫和讀兩個方向,因此可以采用兩個i2c_msg結構體變量,采用結構體數組無疑是一個很好的選擇。讀函數比較好理解,本身沒有存在比較疑惑的地方,只需要注意length是以字節為單位即可。

i2c設備驅動寫函數編寫

在具體講解之前,先看看下面的寫函數,看是否可以發現其中的問題。

 /* 向ap3216c寫多個寄存器數據
  * 入口參數@client:從機地址
  * @reg :寄存器地址
  * @buffer:需要寫入的數據
  * @length:reg/buffer的長度
  */
 static int ap3216c_write_regs(struct i2c_client *client,u8 reg, u8 *buffer, int length)
 {
     int err = 0;
     struct i2c_msg msg[] = {
        {
            .addr  = client->addr,
            .flags = 0,
            .len   = 1,
            .buf   = &reg,
        },
        {
            .addr  = client->addr,
            .flags = 0,
            .len   = length,
            .buf   = buffer,
        },
    };
 
     err = i2c_transfer(client->adapter, msg,2);
     if(err != 2)
    {
         err = -EINVAL;
         printk("write data to ap3216c has been failed\n\r");
    }
     
     return err;
 }

上述函數也是采用了i2c_msg結構體數組進行數據的傳輸,i2c_msg[0].flag = 0,表示是寫,指定了需要寫入的寄存器為reg,i2c_msg[1].flag=0,表示是寫,需要寫入的數據為buffer,根據寫時序圖來看,也沒有什么問題,先是地址,再是數據。按下如下方式編寫程序,編譯並加載至內核中運行,發現讀取出來的數據並不是0x03,而是0x0。程序哪里錯了呢?

 /* open函數 */
 static int ap3216c_open(struct inode *inode, struct file *filp)
 {
     u8 data;
     struct i2c_client *client = (struct i2c_client *)ap3216cdev.private;
 
     filp->private_data = &ap3216cdev;
 
     /* 初始化ap3216c */
     ap3216c_write_reg(client,AP3216C_SYSTEMCONFIG, 0x04); //復位
     mdelay(50);
     ap3216c_write_reg(client,AP3216C_SYSTEMCONFIG, 0x03);//打開三個功能
 
     ap3216c_read_reg(client,AP3216C_SYSTEMCONFIG,&data);
 
     printk("the AP3216c SYSTEMCONFIG = %#x\n\r",data);
 
     return 0;
 }

經過排查,read函數並沒有問題,那就是write函數出問題了,但是根據時序圖來看沒有問題呀,先是寫地址,再是寫數據,難道是時序圖錯了?

如果讀者沒有發現錯誤的話,表示讀者還是沒有正確理解一個i2c_msg是一次完整的單向傳輸以及i2c寫時序可以理解為“一步”的寫操作。

錯誤點分析

首先,i2c_msg是一次完整的單向數據傳輸,也就是每一個完整的i2c_msg傳輸過程中,當flag為0時,那么就意味着在進行寫寄存器操作,並不是說有兩個i2c_msg結構體連續且都為寫的情況下,第二個i2c_msg結構體變量就是向第一個i2c_msg結構體變量中的reg進行寫數據了,i2c_msg成員變量buf遠遠沒有達到這么智能的程度,它只能知道在一個單次且完整的寫過程中,第一個傳遞給buf的是寄存器地址。因此,盡管程序中用了結構體數組來定義了兩個連續的i2c_msg結構體變量,但是它們依舊是兩個單次的寫數據過程,因此i2c_msg[1]還是寫地址,而不是寫數據,所以在指定地址讀取不到0x03,而是其他值(不一定是0x0).

正確函數

 static int ap3216c_write_regs(struct i2c_client *client,u8 reg, u8 *buffer, int length)
 {
     int err = 0;
 
     u8 b[256];
     struct i2c_msg msg;
     b[0] = reg;
     memcpy(&b[1],buffer,length);
 
     msg.addr = client->addr,
     msg.flags = 0;
     msg.len   = length + 1; /* +1是因為還有一個b[0]所存儲的寄存器 */
     msg.buf = b;
 
     err = i2c_transfer(client->adapter, &msg,1);
     if(err != 1)
    {
         err = -EINVAL;
         printk("write data to ap3216c has been failed\n\r");
    }
     
     return err;
 }

在上述函數中,只使用了一個i2c_msg結構變量,因此整個函數是一次完整單向的數據傳輸,使用數組是為了讓數據連續。

 

對於i2c_msg的淺析就到這里了,表達可能有不清除的地方,如果讀者在閱讀的過程中有不明白或者發現錯誤的地方,可以留言探討

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM