在學習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; /* 標志位,指定進行的操作 */
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 = ®,
},
{
.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 = ®,
},
{
.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的淺析就到這里了,表達可能有不清除的地方,如果讀者在閱讀的過程中有不明白或者發現錯誤的地方,可以留言探討