【摘 要】I2C匯流排是一種很通用的匯流排,具有簡單、高效等特點,廣泛應用在各種消費類電子產品及音視頻裝置上,在嵌入式系統的開發中也經常用到。本文分析了嵌入式linux系統中I2C驅動程式的結構,並結合一個具體的I2C時鐘晶片DS1307,說明在嵌入式linux系統下開發I2C裝置驅動程式的一般流程。
【關鍵字】I2C匯流排 嵌入式linux 驅動開發
1、I2C匯流排簡介
I2C (Inter-Integrated Circuit)匯流排是一種由PHILIPS公司開發的兩線式串列匯流排,用於串連微控制器及其外圍裝置。I2C匯流排最主要的優點就是簡單性和有效性。
1.1 I2C匯流排工作原理
I2C匯流排是由資料線SDA和時鐘SCL構成的串列匯流排,各種被控制器件均並聯在這條匯流排上,每個器件都有一個唯一的地址識別,可以作為匯流排上的一個發送器件或接收器件(具體由器件的功能決定) [1]。I2C匯流排的介面電路結構1所示。
圖1 I2C匯流排介面電路[1]
1.2 I2C匯流排的幾種訊號狀態[1]
1. 空閑狀態:SDA和SCL都為高電平。
2. 開始條件(S):SCL為高電平時,SDA由高電平向低電平跳變,開始傳送資料。
3. 結束條件(P):SCL為低電平時,SDA
由低電平向高電平跳變,結束傳送資料。
4. 資料有效:在SCL的高電平期間, SDA保持穩定,資料有效。SDA的改變只能發生在SCL的底電平期間。
5. ACK訊號: 資料轉送的過程中,接收器件每接收一個位元組資料要產生一個ACK訊號,向發送器件發出特定的低電平脈衝,表示已經收到資料。
1.3 I2C匯流排基本操作
I2C匯流排必須由主器件(通常為微控制器)控制,主器件產生串列時鐘(SCL),同時控制匯流排的傳輸方向,併產生開始和停止條件。
資料轉送中,首先主器件產生開始條件,隨後是器件的控制位元組(前七位是從器件的地址,最後一位為讀寫位 )。接下來是讀寫操作的資料,以及 ACK響應訊號。資料轉送結束時,主器件產生停止條件[1]。具體的過程2所示。
圖2 完整的I2C資料轉送過程[1]
2 . Linux下I2C驅動程式的分析
2.1 Linux系統I2C驅動的階層
Linux系統對I2C裝置具有很好的支援,Linux系統下的I2C驅動程式從邏輯上可以分為3個部分:
1. I2C匯流排的驅動 I2C core :實現對I2C匯流排、I2C adapter及I2C driver的管理。
2. I2C控制器的驅動 I2C adapter :針對不同類型的I2C控制器 ,實現對I2C匯流排訪問的具體方法。
3. I2C裝置的驅動 I2C driver :針對特定的I2C裝置,實現具體的功能,包括read, write以及ioctl等對使用者層操作的介面。
這三個部分的層次關係3和圖4所示。
2.2 I2C 匯流排驅動 I2C core
I2C core是Linux核心用來維護和管理的I2C的核心部分,其中維護了兩個靜態List,分別記錄系統中的I2C driver結構和I2C adapter結構。I2C core提供介面函數,允許一個I2C adatper,I2C driver和I2C client初始化時在I2C core中進行註冊,以及退出時進行登出。同時還提供了I2C匯流排讀寫訪問的一般介面(具體的實現在與I2C控制器相關的I2C adapter中實現),主要應用在I2C裝置驅動中。
2.3 I2C控制器的驅動 I2C adapter
I2C adapter是針對不同類型I2C控制器硬體,實現比較底層的對I2C匯流排訪問的具體方法。I2C adapter 構造一個對I2C core層介面的資料結構,並通過介面函數向I2C core註冊一個控制器。
I2C adapter主要實現對I2C匯流排訪問的演算法,iic_xfer() 函數就是I2C adapter底層對I2C匯流排讀寫方法的實現。同時I2C adpter 中還實現了對I2C控制器中斷的處理函數。
2.4 I2C裝置的驅動 I2C driver
I2C driver中提供了一個通用的I2C裝置的驅動程式,實現了字元類型裝置的提供者,對裝置的具體訪問是通過I2C adapter來實現的。I2C driver構造一個對I2C core層介面的資料結構,通過介面函數向 I2C Core註冊一個I2C裝置驅動。同時I2C driver 構造一個對使用者層介面的資料結構,並通過介面函數向核心註冊為一個主裝置號為89的字元類型裝置。
I2C driver實現使用者層對I2C裝置的訪問,包括open,read,write,ioctl,release等常規檔案操作,我們可以通過open函數開啟 I2C的裝置檔案,通過ioctl函數設定要訪問從裝置的地址,然後就可以通過 read和write函數完成對I2C裝置的讀寫操作。
通過I2C driver提供的通用方法可以訪問任何一個I2C的裝置,但是其中實現的read,write及ioctl等功能完全是基於一般裝置的實現,所有的操作資料都是基於位元組流,沒有明確的格式和意義。為了更方便和有效地使用I2C裝置,我們可以為一個具體的I2C裝置開發特定的I2C裝置驅動程式,在驅動中完成對特定的資料格式的解釋以及實現一些專用的功能。
3. 一個具體的I2C裝置驅動程式的開發
DS1307是一款小巧的I2C介面的系統時鐘晶片,具有低功耗,全BCD碼時鐘和日曆輸出, 12 /24小時工作模式,時分秒、星期、年月日計時資料,潤年自動補償,有效期間至2100年,外加56 Bytes的NV RAM(非易失性的RAM)等特點[3]。下面以DS1307為例,說明一個具體的I2C裝置驅動程式的設計要點。
3.1 I2C裝置驅動程式的一般結構
一個具體的I2C裝置驅動需要實現兩個方面的介面,一個是對I2C core層的介面,用以掛接I2C adapter層來實現對I2C匯流排及I2C裝置具體的存取方法,包括要實現attach_adapter,detach_client,command等介面函數。另一個是對使用者應用程式層的介面,提供使用者程式訪問I2C裝置的介面,包括實現open,release,read,write以及最重要的ioctl等標準檔案操作的介面函數。
對I2C core層的介面函數的具體功能解釋如下:
attach_adapter:I2C driver在調用I2C_add_driver() 註冊時,對發現的每一個I2C adapter(對應一條I2C 匯流排)都要調用該函數,檢查該I2C adapter是否符合I2C driver的特定條件,如果符合條件則串連此I2C adapter,並通過I2C adapter來實現對I2C匯流排及I2C裝置的訪問。
detach_client:I2C driver在刪除一個I2C device時調用該函數,清除描述這個I2C device的資料結構,這樣以後就不能訪問該裝置了。
command:針對裝置的特點,實現一系列的子功能,是使用者介面中的ioctl功能的底層實現。
3.2 DS1307驅動程式實現對I2C core層的介面
在驅動中必須實現一個struct i2c_driver 的資料結構,並在驅動模組初始化時向I2C core註冊一個I2C驅動,並完成對I2C adapter的相關操作。
struct i2c_driver ds1307_driver =
{
name: "DS1307",
id: I2C_DRIVERID_DS1307,
flags: I2C_DF_NOTIFY,
attach_adapter:ds1307_probe,
detach_client:ds1307_detach,
command: ds1307_command
};
資料結構ds1307_driver中的name:"DS1307",Id:I2C_DRIVERID_DS1307用來標識DS1307驅動程式。flags: I2C_DF_NOTIFY表示在I2C匯流排發生變化時通知該驅動。
ds1307_probe對應i2c_driver資料結構中的attach_adapter,主要功能:調用 I2C core 層提供的i2c_probe函數尋找一條I2C匯流排,看是否有DS1307的裝置存在,如果存在DS1307,則將對應的I2C adapter 和DS1307裝置掛接在一起,並通過該I2C adapter來實現對DS1307的訪問。同時使能DS1307, 並調用i2c_attach_client()向I2C core層註冊DS1307。
ds1307_detach對應i2c_driver資料結構中的detach_client,主要功能:調用i2c_detach_client() 向I2C core層登出DS1307,並不使能DS1307,這樣I2C驅動就不能訪問DS1307了。
ds1307_command對應i2c_driver資料結構中的command ,主要功能:針對DS1307時鐘晶片的特點,實現一系列的諸如DS1307_GETTIME ,DS1307_SETTIME,DS1307_GETDATETIME,DS1307_MEM_READ,DS1307_MEM_WRITE等子功能,是使用者介面中的ioctl功能的底層實現。
以上3個介面函數使DS1307的驅動程式實現了對I2C 匯流排及I2C adpater的掛接,因此就可以通過I2C core的提供對I2C匯流排讀寫訪問的通用介面,來開發實現DS1037驅動程式對使用者應用程式層的介面函數。
3.3 DS1307驅動程式實現對使用者應用程式層的介面
在驅動中必須實現一個struct file_operations 的資料結構,並向核心註冊為一個字元類型的裝置(用單獨的主裝置號來標識),或者註冊為一個miscdevice裝置(所有miscdevice裝置共同一個主裝置號,不同的次裝置號,所有的miscdevice裝置形成一個鏈表,對裝置訪問時根據次裝置號尋找對應的miscdevice裝置,然後調用其struct file_operations中註冊的應用程式層介面進行操作)。
struct file_operations rtc_fops =
{
owner: THIS_MODULE,
ioctl: ds1307_rtc_ioctl,
read: ds1307_rtc_read,
write: ds1307_rtc_read,
open: ds1307_rtc_open,
release: ds1307_rtc_release
};
資料結構rtc_fops 中的ds1307_rtc_open 和ds1307_rtc_release對應file_operations中的open和release,分別用來開啟和關閉DS1307。
ds1307_rtc_ioctl對應file_operations中的ioctl,對使用者提供的一系列控制時鐘晶片的具體命令:RTC_GET_TIME: 以固定的資料格式讀取系統時鐘的時間。RTC_SET_TIME:以固定的資料格式設定系統時鐘的時間。RTC_SYNC_TIME:系統時鐘和系統時鐘之間的時間同步。
ds1307_rtc_read 對應對應file_operations中的read,實現與ds1307_rtc_ioctl 的子功能RTC_GET_TIME相同的功能,以及從NV RAM讀取資料。
ds1307_rtc_write 對應file_operations中的write,實現與ds1307_rtc_ioctl的子功能 RTC_SET_TIME相同的功能,以及將資料寫入NV RAM。
3.4 DS1307驅動程式的載入和測試
在DS1307驅動模組的初始化函數ds1307_init()中,首先通過i2c_add_driver(&ds1307_driver)向I2C core層註冊一個I2C的裝置驅動,然後再通過misc_register (&ds1307_rtc_miscdev)將DS1307註冊為一個miscdevice裝置,這樣使用者程式就可以通過主裝置號10 次裝置號 135的裝置節點/dev/rtc來訪問DS1307了。
將DS1307的驅動程式編譯成模組的方式,通過insmod命令載入進核心,然後用測試代碼進行測試,DS1307驅動程式中實現的所有功能都達到了預期的效果。
由於DS1307驅動程式在底層實現了對DS1307時鐘晶片資料的解釋和轉換,所以在使用者程式中得到的就是有固定格式和意義的資料,這樣就方便了使用者程式的訪問,提高了應用開發的效率。
4.總結
I2C匯流排是一種結構小巧,協議簡單的匯流排,應用很廣泛,訪問起來簡單方便。linux系統下I2C的驅動程式具有清晰的階層,可以很容易地為一個特定的I2C裝置開發驅動。本文通過對linux系統下I2C驅動,以及一個具體的DS1307時鐘晶片驅動結構的分析,基本上可以很清楚看出一個I2C裝置驅動的開發過程。實現的關鍵分為兩個部分,1. 對I2C core的介面,必須實現 struct i2c_drvier資料結構中的幾個特定的功能函數。這些函數是I2C驅動與I2C匯流排物理層(I2C控制器)和I2C裝置器件之間通訊的基礎。2. 對使用者應用程式層的介面,必須實現struct file_operation資料結構中的一些特定功能的函數,如 open ,release , read ,write,lseek等函數。以上兩類介面中,對I2C core的介面是對I2C裝置訪問的基礎,實現對I2C匯流排具體的存取方法;對使用者應用程式層的介面則是方便應用程式開發,實現裝置特定功能的必不可少的部分。