I2C驅動及裝置節點使用講解
裝載於:http://blog.csdn.net/yhf19881015/article/details/8188809
對於註冊的i2c介面卡,使用者空間也可以使用它們。在Linux核心程式碼檔案/include/linux/i2c-dev.c中針對每個介面卡生成一個主裝置號為89的裝置節點,實現了檔案操作介面,使用者空間可以通過i2c裝置節點訪問i2c介面卡。介面卡的編號從0開始,和介面卡的裝置節點的次裝置號相同。
i2c介面卡的裝置節點是/dev/i2c-x,其中x是數字,代表介面卡的編號。由於介面卡編號是動態分配的(和註冊次序有關),所以想了解哪一個介面卡對應什麼編號,可以檢視/sys/class/i2c-dev/目錄下的檔案內容。
為了在使用者空間的程式當中操作i2c介面卡,必須在程式中包含以下兩句:
#include<linux/i2c-dev.h>
#include<linux/i2c.h>
這兩個標頭檔案中定義了之後需要用到的結構體和巨集。
然後就可以開啟裝置節點了。但是開啟哪一個呢?因為介面卡的編號並不固定。為此我們在中端中執行以下命令:
[[email protected] /]# cat /sys/class/i2c-dev/i2c-0/name
PNX4008-I2C0
[[email protected] /]# cat /sys/class/i2c-dev/i2c-1/name
PNX4008-I2C1
[[email protected] /]# cat /sys/class/i2c-dev/i2c-2/name
USB-I2C
如果我們想開啟第二個介面卡,剛好它的編號是1,對應的裝置節點是/dev/i2c-1。那麼可以用下面的方法開啟它:
int fd;
if ((fd = open("/dev/i2c-1",O_RDWR))< 0) {
/* 錯誤處理 */
exit(1);
}
開啟介面卡對應的裝置節點,i2c-dev為開啟的執行緒建立一個i2c_client,但是這個i2c_client並不加到i2c_adapter的client連結串列當中。當用戶關閉裝置節點時,它自動被釋放。
檢視include/linux/i2c-dev.h檔案,可以看到i2c-dev支援的IOCTL命令。如<!--[if supportFields]> REF _Ref283302932 /h <![endif]-->程式清單 3.1<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->所示。
程式清單 <!--[if supportFields]> STYLEREF 1 /s<![endif]-->3<!--[if supportFields]><![endif]-->.<!--[if supportFields]> SEQ 程式清單 /* ARABIC /s 1<![endif]-->1<!--[if supportFields]><![endif]--> i2c-dev IOCTL命令
#define I2C_RETRIES 0x0701 /*設定收不到ACK時的重試次數 */
#define I2C_TIMEOUT 0x0702 /* 設定超時時限的jiffies */
#define I2C_SLAVE 0x0703 /*設定從機地址 */
#define I2C_SLAVE_FORCE 0x0706 /* 強制設定從機地址 */
#define I2C_TENBIT 0x0704 /*選擇地址位長:=0 for 7bit , != 0 for 10 bit */
#define I2C_FUNCS 0x0705 /*獲取介面卡支援的功能 */
#define I2C_RDWR 0x0707 /*Combined R/W transfer (one STOP only) */
#define I2C_PEC 0x0708 /* != 0 to use PEC with SMBus */
#define I2C_SMBUS 0x0720 /*SMBus transfer */
下面進行一一解釋。
1. 設定重試次數
ioctl(fd, I2C_RETRIES,m);
這句話設定介面卡收不到ACK時重試的次數為m。預設的重試次數為1。
2. 設定超時
ioctl(fd, I2C_TIMEOUT,m);
設定SMBus的超時時間為m,單位為jiffies。
3. 設定從機地址
ioctl(fd, I2C_SLAVE,addr);
ioctl(fd, #defineI2C_SLAVE_FORCE, addr);
在呼叫read()和write()函式之前必須設定從機地址。這兩行都可以設定從機的地址,區別是第二行無論核心中是否已有驅動在使用這個地址都會成功,第一行則只在該地址空閒的情況下成功。由於i2c-dev建立的i2c_client不加入i2c_adapter的client列表,所以不能防止其它執行緒使用同一地址,也不能防止驅動模組佔用同一地址。
4. 設定地址模式
ioctl(file,I2C_TENBIT,select)
如果select不等於0選擇10位元地址模式,如果等於0選擇7位元模式,預設7位元。只有介面卡支援I2C_FUNC_10BIT_ADDR,這個請求才是有效的。
5. 獲取介面卡功能
ioctl(file,I2C_FUNCS,(unsignedlong *)funcs)
獲取的介面卡功能儲存在funcs中。各位元的含義如<!--[if supportFields]>REF _Ref283305554 /h<![endif]-->程式清單 3.2<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->所示。具體的含義可以參考<!--[if supportFields]>REF _Ref283456550 /r /h<![endif]-->第4章<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->。
程式清單 <!--[if supportFields]> STYLEREF 1 /s<![endif]-->3<!--[if supportFields]><![endif]-->.<!--[if supportFields]> SEQ 程式清單 /* ARABIC /s 1<![endif]-->2<!--[if supportFields]><![endif]--> I2C FUNCTIONALILTY
/* include/linux/i2c.h */
#define I2C_FUNC_I2C 0x00000001
#define I2C_FUNC_10BIT_ADDR 0x00000002
#define I2C_FUNC_PROTOCOL_MANGLING 0x00000004/*I2C_M_{REV_DIR_ADDR,NOSTART,..}*/
#define I2C_FUNC_SMBUS_PEC 0x00000008
#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL 0x00008000 /* SMBus 2.0 */
#define I2C_FUNC_SMBUS_QUICK 0x00010000
#define I2C_FUNC_SMBUS_READ_BYTE 0x00020000
#define I2C_FUNC_SMBUS_WRITE_BYTE 0x00040000
#define I2C_FUNC_SMBUS_READ_BYTE_DATA 0x00080000
#define I2C_FUNC_SMBUS_WRITE_BYTE_DATA 0x00100000
#define I2C_FUNC_SMBUS_READ_WORD_DATA 0x00200000
#define I2C_FUNC_SMBUS_WRITE_WORD_DATA 0x00400000
#define I2C_FUNC_SMBUS_PROC_CALL 0x00800000
#define I2C_FUNC_SMBUS_READ_BLOCK_DATA 0x01000000
#define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000
#define I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000/* I2C-like block xfer */
#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 /* w/ 1-byte reg. addr. */
#define I2C_FUNC_SMBUS_READ_I2C_BLOCK_2 0x10000000 /* I2C-like block xfer */
#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK_2 0x20000000 /* w/ 2-byte reg. addr. */
6. I2C層通訊
ioctl(file,I2C_RDWR,(structi2c_rdwr_ioctl_data *)msgset);
這一行程式碼可以使用I2C協議和裝置進行通訊。它進行連續的讀寫,中間沒有間歇。只有當介面卡支援I2C_FUNC_I2C此命令才有效。引數是一個指標,指向一個結構體,它的定義如<!--[if supportFields]>REF _Ref283305956 /h <![endif]-->程式清單 3.3<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->所示。其中i2c_msg的定義參考<!--[if supportFields]> REF _Ref283227534 /h <![endif]-->程式清單 1.7<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->。
程式清單 <!--[if supportFields]> STYLEREF 1 /s<![endif]-->3<!--[if supportFields]><![endif]-->.<!--[if supportFields]> SEQ 程式清單 /* ARABIC /s 1<![endif]-->3<!--[if supportFields]><![endif]--> i2c_rdwr_ioctl_data
struct i2c_rdwr_ioctl_data {
structi2c_msg __user *msgs; /*指向i2c_msgs陣列 */
__u32nmsgs; /* 訊息的個數 */
};
msgs[] 陣列成員包含了指向各自緩衝區的指標。這個函式會根據是否在訊息中的flags置位I2C_M_RD來對緩衝區進行讀寫。從機的地址以及是否使用10位元地址模式記錄在每個訊息中,忽略之前ioctl設定的結果。
7. 設定SMBus PEC
ioctl(file,I2C_PEC,(long )select);
如果select不等於0選擇SMBus PEC (packet error checking),等於零則關閉這個功能,預設是關閉的。
這個命令只對SMBus傳輸有效。這個請求只在介面卡支援I2C_FUNC_SMBUS_PEC時有效;如果不支援這個命令也是安全的,它不做任何工作。
8. SMBus通訊
ioctl(file, I2C_SMBUS, (i2c_smbus_ioctl_data*)msgset);
這個函式和I2C_RDWR類似,引數的指標指向i2c_smbus_ioctl_data型別的變數,它的定義如<!--[if supportFields]> REF _Ref283308161 /h<![endif]-->程式清單 3.4<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->所示。如何填寫i2c_smbus_ioctl_data的各個成員,參考<!--[if supportFields]> REF _Ref283455290 /r/h <![endif]-->4.3<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->節。
程式清單 <!--[if supportFields]> STYLEREF 1 /s<![endif]-->3<!--[if supportFields]><![endif]-->.<!--[if supportFields]> SEQ 程式清單 /* ARABIC /s 1<![endif]-->4<!--[if supportFields]><![endif]--> i2c_smbus_ioctl_data
struct i2c_smbus_ioctl_data {
__u8read_write;
__u8command;
__u32size;
unioni2c_smbus_data __user *data;
};
要想在使用者空間使用i2c介面卡,首先要如3.1<!--[if gte mso 9]><![endif]-->節所示,選擇某個介面卡的裝置節點開啟,然後才能進行通訊。
通訊的方式有兩種,一種是使用操作普通檔案的介面read()和write()。這兩個函式間接呼叫了i2c_master_recv和i2c_master_send。但是在使用之前需要使用I2C_SLAVE設定從機地址,設定可能失敗,需要檢查返回值。這種通訊過程進行I2C層的通訊,一次只能進行一個方向的傳輸。
下面的程式是ARM與E2PROM晶片通訊的例子,如<!--[if supportFields]> REF _Ref283651035 /h<![endif]-->程式清單 3.5<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->所示。
程式清單 <!--[if supportFields]> STYLEREF 1 /s<![endif]-->3<!--[if supportFields]><![endif]-->.<!--[if supportFields]> SEQ 程式清單 /* ARABIC /s 1<![endif]-->5<!--[if supportFields]><![endif]--> 使用read()/write()與i2c裝置通訊
#include <stdio.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#define CHIP "/dev/i2c-0"
#define CHIP_ADDR 0x50
int main()
{
printf("hello,this is i2c tester/n");
int fd =open(CHIP, O_RDWR);
if (fd< 0) {
printf("open"CHIP"failed/n");
gotoexit;
}
if (ioctl(fd,I2C_SLAVE_FORCE, CHIP_ADDR) < 0) { /*設定晶片地址 */
printf("oictl:setslave address failed/n");
gotoclose;
}
struct i2c_msg msg;
unsigned char rddata;
unsigned char rdaddr[2] = {0, 0}; /* 將要讀取的資料在晶片中的偏移量 */
unsigned char wrbuf[3] = {0, 0, 0x3c}; /* 要寫的資料,頭兩位元組為偏移量 */
printf("inputa char you want to write to E2PROM/n");
wrbuf[2]= getchar();
printf("writereturn:%d, write data:%x/n", write(fd, wrbuf, 3), wrbuf[2]);
sleep(1);
printf("writeaddress return: %d/n",write(fd, rdaddr, 2)); /* 讀取之前首先設定讀取的偏移量 */
printf("readdata return:%d/n", read(fd, &rddata, 1));
printf("rddata:%c/n", rddata);
close:
close(fd);
exit:
return0;
}
還可以使用I2C_RDWR實現同樣的功能,如<!--[if supportFields]> REF _Ref283651333 /h<![endif]-->程式清單 3.6<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->所示。此時ioctl返回的值為執行成功的訊息數。
程式清單 <!--[if supportFields]> STYLEREF 1 /s<![endif]-->3<!--[if supportFields]><![endif]-->.<!--[if supportFields]> SEQ 程式清單 /* ARABIC /s 1<![endif]-->6<!--[if supportFields]><![endif]--> 使用I2C_RDWR與I2C裝置通訊
#include <stdio.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#define CHIP "/dev/i2c-0"
#define CHIP_ADDR 0x50
int main()
{
printf("hello,this is i2c tester/n");
int fd =open(CHIP, O_RDWR);
if (fd< 0) {
printf("open"CHIP"failed/n");
gotoexit;
}
struct i2c_msg msg;
unsigned char rddata;
unsigned char rdaddr[2] = {0, 0};
unsigned char wrbuf[3] = {0, 0, 0x3c};
printf("inputa char you want to write to E2PROM/n");
wrbuf[2]= getchar();
struct i2c_rdwr_ioctl_data ioctl_data;
struct i2c_msg msgs[2];
msgs[0].addr= CHIP_ADDR;
msgs[0].len= 3;
msgs[0].buf= wrbuf;
ioctl_data.nmsgs= 1;
ioctl_data.msgs= &msgs[0];
printf("ioctlwrite,return :%d/n", ioctl(fd, I2C_RDWR, &ioctl_data));
sleep(1);
msgs[0].addr= CHIP_ADDR;
msgs[0].len= 2;
msgs[0].buf= rdaddr;
msgs[1].addr= CHIP_ADDR;
msgs[1].flags|= I2C_M_RD;
msgs[1].len= 1;
msgs[1].buf= &rddata;
ioctl_data.nmsgs= 1;
ioctl_data.msgs= msgs;
printf("ioctlwrite address, return :%d/n", ioctl(fd, I2C_RDWR, &ioctl_data));
ioctl_data.msgs= &msgs[1];
printf("ioctlread, return :%d/n", ioctl(fd, I2C_RDWR, &ioctl_data));
printf("rddata:%c/n", rddata);
close:
close(fd);
exit:
return0;
}