歡迎您光臨本站 註冊首頁

嵌入式Linux系統下I2C設備驅動程序的開發

←手機掃碼閱讀     火星人 @ 2014-03-26 , reply:0

【摘 要】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 . LinuxI2C驅動程序的分析
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匯流排具體的訪問方法;對用戶應用層的介面則是方便應用程序開發,實現設備特定功能的必不可少的部分。
參考文獻:
[1] Philips Corporation,I2C bus specification version 2.1,2000
[2] Linux kernel,version 2.4.30
[3] Maxim Integrated Products , inc . USA . DS1307 Datasheet , 2004
[4] Aless and Robin著,魏永明等譯,《LINUX設備驅動程序(第二版)》,北京,中國電力出版社,2004年

[火星人 ] 嵌入式Linux系統下I2C設備驅動程序的開發已經有1517次圍觀

http://coctec.com/docs/linux/show-post-189040.html