============================================
作者:yuanlulu
http://blog.csdn.NET/yuanlulu
版權沒有,但是轉載請保留此段聲明
============================================
第1章 用戶空間使用i2c_dev
對於注冊的i2c適配器,用戶空間也可以使用它們。在Linux內核代碼文件/include/linux/i2c-dev.c中針對每個適配器生成一個主設備號為89的設備節點,實現了文件操作接口,用戶空間可以通過i2c設備節點訪問i2c適配器。適配器的編號從0開始,和適配器的設備節點的次設備號相同。
i2c適配器的設備節點是/dev/i2c-x,其中x是數字,代表適配器的編號。由於適配器編號是動態分配的(和注冊次序有關),所以想了解哪一個適配器對應什么編號,可以查看/sys/class/i2c-dev/目錄下的文件內容。
1.1 前期准備
為了在用戶空間的程序當中操作i2c適配器,必須在程序中包含以下兩句:
#include<linux/i2c-dev.h>
#include<linux/i2c.h>
這兩個頭文件中定義了之后需要用到的結構體和宏。
然后就可以打開設備節點了。但是打開哪一個呢?因為適配器的編號並不固定。為此我們在中端中運行以下命令:
[root@zlg /]# cat /sys/class/i2c-dev/i2c-0/name
PNX4008-I2C0
[root@zlg /]# cat /sys/class/i2c-dev/i2c-1/name
PNX4008-I2C1
[root@zlg /]# 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鏈表當中。當用戶關閉設備節點時,它自動被釋放。
1.2 IOCTL控制
查看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;
};
1.3 i2c_dev使用例程
要想在用戶空間使用i2c適配器,首先要如3.1<!--[if gte mso 9]><![endif]-->節所示,選擇某個適配器的設備節點打開,然后才能進行通信。
1.3.1 read()/write()
通信的方式有兩種,一種是使用操作普通文件的接口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;
unsignedchar rddata;
unsignedchar rdaddr[2] = {0, 0}; /* 將要讀取的數據在芯片中的偏移量 */
unsignedchar 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;
}
1.3.2 I2C_RDWR
還可以使用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;
unsignedchar rddata;
unsignedchar rdaddr[2] = {0, 0};
unsignedchar wrbuf[3] = {0, 0, 0x3c};
printf("inputa char you want to write to E2PROM/n");
wrbuf[2]= getchar();
structi2c_rdwr_ioctl_data ioctl_data;
structi2c_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;
}