Linux下I2C設備驅動(dòng)開(kāi)發(fā)和實(shí)現
I2C總線(xiàn)具有結構簡(jiǎn)單使用方便的特點(diǎn)。本文描述了linux下I2C驅動(dòng)的結構,并在此基礎上給出了I2C設備驅動(dòng)和應用的實(shí)現。
1 引言
I2C (Inter-Integrated Circuit)總線(xiàn)是一種由PHILIPS公司開(kāi)發(fā)的兩線(xiàn)式串行總線(xiàn),用于連接微控制器及其外圍設備。I2C總線(xiàn)最主要的優(yōu)點(diǎn)是其簡(jiǎn)單性和有效性。由于接口直接在組件之上,因此I2C總線(xiàn)占用的空間非常小,減少了電路板的空間和芯片管腳的數量,降低了互聯(lián)成本。I2C總線(xiàn)最初為音頻和視頻設備開(kāi)發(fā),現已應用于各種服務(wù)與管理場(chǎng)合,來(lái)實(shí)現配置或掌握組件的功能狀態(tài),如電源、系統風(fēng)扇、系統溫度等參數,增加了系統的安全性,方便了管理。
2 I2C總線(xiàn)概述
I2C總線(xiàn)是由數據線(xiàn)SDA和時(shí)鐘SCL構成的串行總線(xiàn),可發(fā)送和接收數據,每個(gè)器件都有一個(gè)惟一的地址識別。I2C 規程運用主/從雙向通訊。器件發(fā)送數據到總線(xiàn)上,則定義為發(fā)送器,器件接收數據則定義為接收器。主器件和從器件都可以工作于接收和發(fā)送狀態(tài)??偩€(xiàn)必須由主器件(通常為微控制器)控制,主器件產(chǎn)生串行時(shí)鐘(SCL)控制總線(xiàn)的傳輸方向,并產(chǎn)生起始和停止條件。SDA線(xiàn)上的數據狀態(tài)僅在SCL為低電平的期間才能改變,SCL為高電平的期間,SDA狀態(tài)的改變被用來(lái)表示起始和停止條件。
I2C總線(xiàn)在傳送數據過(guò)程中共有三種類(lèi)型信號,它們分別是:開(kāi)始信號、結束信號和應答信號。
開(kāi)始信號:SCL為高電平時(shí),SDA由高電平向低電平跳變,開(kāi)始傳送數據。
結束信號:SCL為低電平時(shí),SDA由低電平向高電平跳變,結束傳送數據。
應答信號:接收數據的IC在接收到8bit數據后,向發(fā)送數據的IC發(fā)出特定的低電平脈沖,表示已收到數據。CPU向受控單元發(fā)出一個(gè)信號后,等待受控單元發(fā)出一個(gè)應答信號,CPU接收到應答信號后,根據實(shí)際情況作出是否繼續傳遞信號的判斷。若未收到應答信號,由判斷為受控單元出現故障。
Linux中I2C總線(xiàn)的驅動(dòng)分為兩個(gè)部分,總線(xiàn)驅動(dòng)(BUS)和設備驅動(dòng)(DEVICE)。其中總線(xiàn)驅動(dòng)的職責,是為系統中每個(gè)I2C總線(xiàn)增加相應的讀寫(xiě)方法。但是總線(xiàn)驅動(dòng)本身并不會(huì )進(jìn)行任何的通訊,它只是存在那里,等待設備驅動(dòng)調用其函數,參見(jiàn)圖1。
設備驅動(dòng)則是與掛在I2C總線(xiàn)上的具體的設備通訊的驅動(dòng)。通過(guò)I2C總線(xiàn)驅動(dòng)提供的函數,設備驅動(dòng)可以忽略不同總線(xiàn)控制器的差異,不考慮其實(shí)現細節地與硬件設備通訊。
圖1 Linux內核I2C總線(xiàn)驅動(dòng)程序構架
在我們的Linux驅動(dòng)的i2c文件夾下有algos,busses,chips三個(gè)文件夾,另外還有i2c-core.c和i2c-dev.c兩個(gè)文件。其中i2c-core.c文件實(shí)現了I2C core框架,是Linux內核用來(lái)維護和管理的I2C的核心部分,其中維護了兩個(gè)靜態(tài)的List,分別記錄系統中的I2C driver結構和I2C adapter結構。I2C core提供接口函數,允許一個(gè)I2C adatper,I2C driver和I2C client初始化時(shí)在I2C core中進(jìn)行注冊,以及退出時(shí)進(jìn)行注銷(xiāo)。同時(shí)還提供了I2C總線(xiàn)讀寫(xiě)訪(fǎng)問(wèn)的一般接口,主要應用在I2C設備驅動(dòng)中。
Busses文件夾下的i2c-mpc.c文件實(shí)現了PowerPC下I2C總線(xiàn)適配器驅動(dòng),定義描述了具體的I2C總線(xiàn)適配器的i2c_adapter數據結構,實(shí)現比較底層的對I2C總線(xiàn)訪(fǎng)問(wèn)的具體方法。I2C adapter 構造一個(gè)對I2C core層接口的數據結構,并通過(guò)接口函數向I2C core注冊一個(gè)控制器。I2C adapter主要實(shí)現對I2C總線(xiàn)訪(fǎng)問(wèn)的算法,iic_xfer() 函數就是I2C adapter底層對I2C總線(xiàn)讀寫(xiě)方法的實(shí)現。同時(shí)I2C adpter 中還實(shí)現了對I2C控制器中斷的處理函數。
i2c-dev.c文件中實(shí)現了I2C driver,提供了一個(gè)通用的I2C設備的驅動(dòng)程序,實(shí)現了字符類(lèi)型設備的訪(fǎng)問(wèn)接口,實(shí)現了對用戶(hù)應用層的接口,提供用戶(hù)程序訪(fǎng)問(wèn)I2C設備的接口,包括實(shí)現open,release,read,write以及最重要的ioctl等標準文件操作的接口函數。我們可以通過(guò)open函數打開(kāi) I2C的設備文件,通過(guò)ioctl函數設定要訪(fǎng)問(wèn)從設備的地址,然后就可以通過(guò) read和write函數完成對I2C設備的讀寫(xiě)操作。
通過(guò)I2C driver提供的通用方法可以訪(fǎng)問(wèn)任何一個(gè)I2C的設備,但是其中實(shí)現的read,write及ioctl等功能完全是基于一般設備的實(shí)現,所有的操作數據都是基于字節流,沒(méi)有明確的格式和意義。為了更方便和有效地使用I2C設備,我們可以為一個(gè)具體的I2C設備開(kāi)發(fā)特定的I2C設備驅動(dòng)程序,在驅動(dòng)中完成對特定的數據格式的解釋以及實(shí)現一些專(zhuān)用的功能。
4 Linux下I2C具體驅動(dòng)開(kāi)發(fā)
TMP75是TI公司推出的基于I2C總線(xiàn)的數字溫度傳感器,具有低的功耗,高數字分辨率,廣泛應用于電源溫度監控,計算機外設保護,筆記本和蜂窩電話(huà)中。針對該設備開(kāi)發(fā)驅動(dòng)程序,由于linux系統下已經(jīng)實(shí)現了I2C core框架,I2C總線(xiàn)適配器驅動(dòng),同時(shí)通過(guò)i2c-dev.c文件提供了一個(gè)通用的I2C設備的驅動(dòng)程序,因此我們的驅動(dòng)程序的開(kāi)發(fā)主要集中在TMP75設備驅動(dòng)程序這一層,用來(lái)實(shí)現針對TMP75設備的數據格式的解釋以及實(shí)現一些專(zhuān)用的功能。
根據TMP75的具體寄存器地址和功能定義:
#define TMP75_REG_TEMP 0x00 //溫度寄存器地址
#define TMP75_REG_CONF 0x01 //配置寄存器地址
#define TMP75_REG_TEMP_LOW 0x02 //低溫閾值寄存器地址
#define TMP75_REG_TEMP_HIGH 0x03 //高溫閾值寄存器地址
定義一個(gè)TMP75_data結構體和一系列函數實(shí)現總線(xiàn)初始化時(shí)的設備檢測加載、設備刪除時(shí)的數據操作。
struct TMP75_data {
struct i2c_client client;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u16 temp_input; /* Register values */
u16 temp_max;
u16 temp_hyst;
};
static int TMP 75_attach_adapter(struct i2c_adapter *adapter);
static int TMP 75_detect(struct i2c_adapter *adapter,int address,int kind);
static void TMP 75_init_client(struct i2c_client *client);
static int TMP 75_detach_client(struct i2c_client *client);
static int TMP 75_read_value(struct i2c_client *client,u8 reg);
static int TMP 75_write_value(struct i2c_client *client,u8 reg,u16 value);
static struct TMP 75_data *tmp75_update_device(struct device *dev);
其中針對TMP75設備寄存器的特定格式定義TMP75寄存器讀寫(xiě)的兩個(gè)函數如下:
static int TMP75_write_value(struct i2c_client *client,u8 reg,u16 value)
{
if (reg == TMP75_REG_CONF)
return i2c_smbus_write_byte_data(client,reg,value);
else
return i2c_smbus_write_word_data(client,reg,swab16(value));
}
static int TMP75_read_value(struct i2c_client *client,u8 reg)
{
if (reg == TMP 75_REG_CONF)
return i2c_smbus_read_byte_data(client,reg);
else
return swab16(i2c_smbus_read_word_data(client,reg));
}
具體的設備驅動(dòng)程序完成之后將TMP75設備驅動(dòng)的配置選項添加到chips文件夾下的kconfig文件中,這樣在配置內核選項時(shí)就可以把TMP75設備驅動(dòng)添加到內核中。
5 Linux下I2C應用程序開(kāi)發(fā)
Linux中應用程序要使用本驅動(dòng)來(lái)訪(fǎng)問(wèn)外部I2C器件,首先要通過(guò)open()來(lái)打開(kāi)其驅動(dòng),使用完畢后使用close()將其關(guān)閉。
int fd;
fd = open(/dev/i2c/0,O_RDWR);
……
close(fd);
I2C總線(xiàn)控制器驅動(dòng)提供的API函數提供了ioctl()函數用于設定I2C總線(xiàn)控制器的一些參數,本應用程序調用ioctl函數將I2C總線(xiàn)設置為7位地址模式,同時(shí)設置I2C從機地址。
ioctl(fd,I2C_TENBIT,0)
ioctl(fd,I2C_SLAVE,SLAVE_ADDR)
對TMP75的初始化工作通過(guò)調用write()函數實(shí)現,通過(guò)調用該函數實(shí)現對配置寄存器、高溫閾值和低溫閾值寄存器的初始化配置。
//配置寄存器的初始化
senbuf[0]=0x01;
senbuf[1]=I2C_CONF_INITDATA;
write(fd,sendbuf,2);
對TMP75當前工作溫度的讀取通過(guò)調用write()函數先寫(xiě)入溫度寄存器的地址,然后調用read()函數讀取寄存器2字節的溫度數據實(shí)現。
write(fd,0x0,1);
read(fd,recbuf,2);
6 總結
I2C總線(xiàn)結構簡(jiǎn)單使用方便。linux系統下I2C的驅動(dòng)程序具有清晰的層次結構,借助于成熟的驅動(dòng)的例子用戶(hù)很容易開(kāi)發(fā)出針對自己產(chǎn)品的相應驅動(dòng)。本文分析了Linux系統下I2C驅動(dòng)結構,并在此基礎上實(shí)現了一個(gè)具體的I2C設備的驅動(dòng),并在此基礎上給出了對I2C總線(xiàn)實(shí)現訪(fǎng)問(wèn)的用戶(hù)應用實(shí)現。
評論