Linux i2c-dev簡析
上篇文章簡要分析了如何編寫一個(gè)Linux下的I2C設(shè)備驅(qū)動(dòng)程序。
編寫驅(qū)動(dòng)程序畢竟有一定的門檻,需要熟悉內(nèi)核各種相關(guān)的開發(fā)規(guī)范,
有時(shí)為了快速的測試一款I(lǐng)2C設(shè)備的功能,臨時(shí)編寫驅(qū)動(dòng)程序可能會使得工期比較緊張;
并且
libi2c.so libi2c.so.0 libi2c.so.0.1.1
有時(shí)I2C設(shè)備
十分的簡單,為此編寫一個(gè)單獨(dú)的驅(qū)動(dòng)程序未免有點(diǎn)“興師動(dòng)眾”。
i2c-dev框架很好的解決了上面的問題,使用該框架可以使我們在用戶空間上編寫I2C通信程序。
i2c-dev在內(nèi)核中封裝了關(guān)于I2C通信所需要的所有通信細(xì)節(jié),通過ioctl接口將這些功能暴露
給用戶空間程序調(diào)用。用戶應(yīng)用程序使用open/read/write/ioctl系統(tǒng)嗲用就可實(shí)現(xiàn)與I2C設(shè)備
的通信。
基本原理
在Linux系統(tǒng)下,每個(gè)使能的I2C適配器,在/dev目錄下都會創(chuàng)建一個(gè)字符設(shè)備文件
(主設(shè)備號89),例如/dev/i2c-0,通過這個(gè)設(shè)備文件,就可以實(shí)現(xiàn)與I2C設(shè)備的通信,
當(dāng)然,I2C設(shè)備必須首先掛載在該I2C適配器之下。
i2c-dev的內(nèi)核代碼可以參考這里。其主要的功能就是創(chuàng)建I2C適配器字符設(shè)備,
并提供如下的功能:
static const struct file_operations i2cdev_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .read = i2cdev_read, .write = i2cdev_write, .unlocked_ioctl = i2cdev_ioctl, .compat_ioctl = compat_i2cdev_ioctl, .open = i2cdev_open, .release = i2cdev_release, };
可以看到i2c-dev提供了read、write、ioctl功能。這里需要注意的是:
read和write方法不支持RepStart模式,也就是每次調(diào)用只能發(fā)送/接收一個(gè)字節(jié)的數(shù)據(jù),
這對于操作稍微復(fù)雜點(diǎn)的I2C設(shè)備的局限性太大,一般不會使用這兩個(gè)接口。
ioctl提供了I2C復(fù)合數(shù)據(jù)傳輸和SMbus傳輸兩種更為通用的數(shù)據(jù)傳輸方式,
它們支持一次發(fā)送、接收多個(gè)字節(jié)數(shù)據(jù),所以,一般選擇這種方式與I2C設(shè)備進(jìn)行通信。
配置命令
i2c-dev.h #define I2C_RETRIES 0x0701 /* 通信未響應(yīng)時(shí)的重試次數(shù)*/ #define I2C_TIMEOUT 0x0702 /* 設(shè)置通信的超時(shí)時(shí)間,單位:jiffies */ /* NOTE: Slave address is 7 or 10 bits, but 10-bit addresses * are NOT supported! (due to code brokenness) */ #define I2C_SLAVE 0x0703 /* 設(shè)置從設(shè)備地址 */ #define I2C_SLAVE_FORCE 0x0706 /* 當(dāng)該設(shè)備地址被某個(gè)驅(qū)動(dòng)程序使用時(shí),強(qiáng)制設(shè)置設(shè)備地址*/ #define I2C_TENBIT 0x0704 /* 0 for 7 bit addrs, != 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 */
具體到用戶空間的i2c通信,對于這些配置命令的使用方式一般如下:
調(diào)用ioctl(xxx_fd, I2C_SLAVE/I2C_SLAVE_FORCE, xx),設(shè)置i2c設(shè)備通信地址。
調(diào)用ioctl(xxx_fd, I2C_RETRIES, xx),設(shè)置通信未響應(yīng)時(shí)的重試次數(shù)
調(diào)用ioctl(xxx_fd, I2C_TIMEOUT, xx),設(shè)置通信超時(shí)時(shí)間
調(diào)用ioctl(xxx_fd, I2C_FUNCS, xx),查詢適配支持的功能掩碼
調(diào)用ioctl(xxx_fd, I2C_RDWR, xx)或者ioctl(xxx_fd, I2C_SMBUS, xx)進(jìn)行數(shù)據(jù)傳輸
數(shù)據(jù)傳輸
#define I2C_FUNC_I2C 0x00000001 #define I2C_FUNC_10BIT_ADDR 0x00000002 #define I2C_FUNC_PROTOCOL_MANGLING 0x00000004 /* I2C_M_IGNORE_NAK etc. */ #define I2C_FUNC_SMBUS_PEC 0x00000008 #define I2C_FUNC_NOSTART 0x00000010 /* I2C_M_NOSTART */ #define I2C_FUNC_SLAVE 0x00000020 #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_HOST_NOTIFY 0x10000000
I2C復(fù)合數(shù)據(jù)傳輸
所謂的I2C復(fù)合數(shù)據(jù)傳輸,就是基于I2C通信時(shí)序,發(fā)送復(fù)合的數(shù)據(jù),該模式支持RepStart,
可以改變數(shù)據(jù)的發(fā)送方向,每次傳輸過程只有一個(gè)結(jié)束信號。
該功能需要I2C適配器對于I2C_FUNC_I2C功能的支持。
發(fā)送和接收的數(shù)據(jù)通過i2c_msg進(jìn)行封裝,
關(guān)于i2c_msg我們在如何編寫一個(gè)Linux下的I2C設(shè)備驅(qū)動(dòng)程序有過介紹,
然后將i2c_msg數(shù)組加入到i2c_rdwr_ioctl_data結(jié)構(gòu)中,
struct i2c_rdwr_ioctl_data { struct i2c_msg __user *msgs; /* pointers to i2c_msgs */ __u32 nmsgs; /* number of i2c_msgs */ };
_u8 _buf[] = {0x01, 0x02, 0x03}; _u8 write_buf[16] = {0}; struct i2c_msg[2] = msgs{ { .addr = 0x48, .flags = 0, //寫數(shù)據(jù) .len = 3, .buf = write_buf, } { .addr = 0x0f, .flags = I2C_M_RD,//讀數(shù)據(jù) .len = 6, .buf = read_buf, }; struct i2c_rdwr_ioctl_data ioctl_data = { .msgs = msgs, .nmsgs = 2, }; /**/ fd = open("/dev/i2c-x", O_RDWR); /**/ ioctl(fd, I2C_SLAVE, addr); /**/ ioctl(fd, I2C_RDWR, &ioctl_data);
————————————————
SMbus傳輸
SMbus在如何編寫一個(gè)Linux下的I2C設(shè)備驅(qū)動(dòng)程序同樣有介紹,如果I2C適配器支持SMbus協(xié)議,
那么就可以使用SMbus協(xié)議與設(shè)備進(jìn)行通信。查看I2C適配器關(guān)于SMbus支持情況,
可以通過ioctl的I2C_FUNCS命令查詢。
使用SMbus進(jìn)行數(shù)據(jù)傳輸,需要將數(shù)據(jù)封裝到i2c_smbus_ioctl_data中
struct i2c_smbus_ioctl_data { __u8 read_write; __u8 command; __u32 size; union i2c_smbus_data __user *data; }; #define I2C_SMBUS_BLOCK_MAX 32 /* As specified in SMBus standard */ union i2c_smbus_data { __u8 byte; __u16 word; __u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */ /* and one more for user-space compatibility */ }; /* i2c_smbus_xfer read or write markers */ #define I2C_SMBUS_READ 1 #define I2C_SMBUS_WRITE 0
#define I2C_SMBUS_QUICK 0 #define I2C_SMBUS_BYTE 1 #define I2C_SMBUS_BYTE_DATA 2 #define I2C_SMBUS_WORD_DATA 3 #define I2C_SMBUS_PROC_CALL 4 #define I2C_SMBUS_BLOCK_DATA 5 #define I2C_SMBUS_I2C_BLOCK_BROKEN 6 #define I2C_SMBUS_BLOCK_PROC_CALL 7 /* SMBus 2.0 */ #define I2C_SMBUS_I2C_BLOCK_DATA 8
最后,通過ioctl(xxx_fd, I2C_SMBUS, xx)進(jìn)行數(shù)據(jù)傳輸。
SMbus的數(shù)據(jù)傳輸協(xié)議比較復(fù)雜,
i2ctools中的smbus.c對每個(gè)傳輸協(xié)議都進(jìn)行了封裝,我們可以直接借鑒,參考i2ctools一節(jié)。
i2ctools
i2ctools就是基于i2c-dev實(shí)現(xiàn)的i2c相關(guān)的工具集合,其功能十分的強(qiáng)大,
i2cdetect可以實(shí)現(xiàn)系統(tǒng)中i2c設(shè)備的基本探測,i2ctransfer可以進(jìn)行i2c數(shù)據(jù)傳輸。
在嵌入式開發(fā)中,我們可以借助這些工具來對I2C設(shè)備進(jìn)行基本管理和通信協(xié)議測試。
編譯
tar zxvf i2c-tools-4.1.tar.gz cd i2c-tools-4.1
修改Makefile文件編譯器相關(guān)配置。
CC := arm-linux-gcc
AR := arm-linux-ar
STRIP := arm-linux-strip
編譯完成之后,可以到tools目錄下獲得i2cdetect和i2ctransfer工具,
lib和include目錄分別是smbus二次開發(fā)庫,我們可以直接使用。
Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST] i2cdetect -F I2CBUS i2cdetect -l I2CBUS is an integer or an I2C bus name If provided, FIRST and LAST limit the probing range.
查詢系統(tǒng)i2c適配器列表
root~$ i2cdetect -l i2c-0 i2c 21a0000.i2c I2C adapter
查詢i2c bus的支持的功能
root@zpd ~$ i2cdetect -F 21a0000.i2c 或者是 0
Functionalities implemented by /dev/i2c-0:
I2C yes
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call yes
SMBus Block Write yes
SMBus Block Read no
SMBus Block Process Call no
SMBus PEC yes
I2C Block Write yes
I2C Block Read yes
根據(jù)這些,可以確定該i2c bus所支持的I2C通信方式。
查詢i2c bus下的設(shè)備地址
root@zpd ~$ i2cdetect -y 0 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- UU -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- UU UU -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --i2ctransfer
基本格式
Usage: i2ctransfer I2CBUS DESC [DATA] [DESC [DATA]]... I2CBUS is an integer or an I2C bus name
其中,I2CBUS表示i2c bus
DESC表示傳輸格式,{r|w}LENGTH[@address],比如,
w2@0x48,表示向地址為0x48的設(shè)備寫入2個(gè)字節(jié),DATA表示字節(jié)序列
可以沒有DATA字段,比如DESC為r2@0x48,從地址為0x48的設(shè)備中,讀取2個(gè)字節(jié)。
DESC和DATA可以多次出現(xiàn),比如,i2ctransfer 0 w1@0x32 0x10 r1,
表示,首先向地址為0x32的設(shè)備寫入一個(gè)字節(jié)0x10,這個(gè) 0x10表示設(shè)備一個(gè)寄存器地址,
然后,從該寄存器讀取一個(gè)字節(jié)數(shù)據(jù)。
讀取實(shí)例
我們通過一個(gè)實(shí)例演示如何通過,i2ctransfer與i2c設(shè)備通信。
rtc-rx8010是一個(gè)通過I2C通信的RTC芯片,
我們可以通過i2ctransfer直接讀取當(dāng)前的時(shí)間。具體命令如下:
i2ctransfer 0 w1@0x32 0x10 r7 0x28 0x13 0x15 0x02 0x04 0x08 0x20 //2020-08-04 15:13:28 Tue
其中, w1@0x32 0x10表示首先寫入RTC代表的SEC的寄存器地址,
r7表示以SEC寄存開始,連續(xù)讀取7個(gè)數(shù)據(jù),分別為SEC MIN HOUR WEEK DAY MONTH YEAR
smbus二次開發(fā)
libi2c.so libi2c.so.0 libi2c.so.0.1.1
smbus.h中封裝的smbus通信接口。
extern __s32 i2c_smbus_access(int file, char read_write, __u8 command, int size, union i2c_smbus_data *data); extern __s32 i2c_smbus_write_quick(int file, __u8 value); extern __s32 i2c_smbus_read_byte(int file); extern __s32 i2c_smbus_write_byte(int file, __u8 value); extern __s32 i2c_smbus_read_byte_data(int file, __u8 command); extern __s32 i2c_smbus_write_byte_data(int file, __u8 command, __u8 value); extern __s32 i2c_smbus_read_word_data(int file, __u8 command); extern __s32 i2c_smbus_write_word_data(int file, __u8 command, __u16 value); extern __s32 i2c_smbus_process_call(int file, __u8 command, __u16 value); /* Returns the number of read bytes */ extern __s32 i2c_smbus_read_block_data(int file, __u8 command, __u8 *values); extern __s32 i2c_smbus_write_block_data(int file, __u8 command, __u8 length, const __u8 *values); /* Returns the number of read bytes */ /* Until kernel 2.6.22, the length is hardcoded to 32 bytes. If you ask for less than 32 bytes, your code will only work with kernels 2.6.23 and later. */ extern __s32 i2c_smbus_read_i2c_block_data(int file, __u8 command, __u8 length, __u8 *values); extern __s32 i2c_smbus_write_i2c_block_data(int file, __u8 command, __u8 length, const __u8 *values); /* Returns the number of read bytes */ extern __s32 i2c_smbus_block_process_call(int file, __u8 command, __u8 length, __u8 *values);
————————————————
原文鏈接:https://blog.csdn.net/lhl_blog/article/details/107787881
*博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請聯(lián)系工作人員刪除。