嵌入式Linux中I2C设备驱动程序的研究与实现
?
I2C是“Inter?Integrated?Circuit?Bus”的縮寫,中文譯成“內(nèi)部集成電路總線”,?它是Philips?公司于20?世紀(jì)80?年代研發(fā)成功的一種具有多端控制功能的雙線雙向串行數(shù)據(jù)總線標(biāo)準(zhǔn),?其具有模塊化、電路結(jié)構(gòu)簡(jiǎn)單等優(yōu)點(diǎn)。在嵌入式系統(tǒng)中,I2C總線已經(jīng)成為器件接口的標(biāo)準(zhǔn)之一,?常用于連接RAM、EEPROM?以及LCD?控制器等設(shè)備。另外,總線的數(shù)據(jù)傳輸是以字節(jié)為單位的。
目前,標(biāo)準(zhǔn)的I2C的傳輸速率可以達(dá)到100kbit/s,能支持128?個(gè)設(shè)備,增強(qiáng)型I2C傳輸速率可達(dá)400kbit/s,能支持多達(dá)1024?個(gè)設(shè)備,高速模式下的I2C?傳輸速率更高達(dá)3.4Mbit/s。
?序就可以輕松地操作和驅(qū)動(dòng)硬件架構(gòu)的分層。
2?Linux?的I2C?體系結(jié)構(gòu)
2.1?Linux?下I2C?體系結(jié)構(gòu)分析
Linux?的I2C?體系結(jié)構(gòu)由3?大部分組成:
(1)I2C框架:I2C.h?和I2C-core.c?為I2C框架的主體,提供了核心數(shù)據(jù)結(jié)構(gòu)的定義、I2C?適配器驅(qū)動(dòng)和設(shè)備驅(qū)動(dòng)的注冊(cè)、注銷方法,I2C?通信方法(algorithm)上層的、與具體適配器無(wú)關(guān)的代碼、以及檢測(cè)設(shè)備地址的上層代碼等。作為核心的I2C-core.c?還為總線驅(qū)動(dòng)設(shè)備提供了一些統(tǒng)一的調(diào)用接口進(jìn)行讀寫和設(shè)置操作,?另外它還提供了將各種支持的總線設(shè)備驅(qū)動(dòng)添加到這個(gè)體系中的方法,?以及當(dāng)不再使用這些總線驅(qū)動(dòng)時(shí)從體系中刪除的方法。
(2)I2C?總線驅(qū)動(dòng)I2C總線驅(qū)動(dòng)是對(duì)I2C?硬件體系結(jié)構(gòu)中適配器端的實(shí)現(xiàn),I2C?總線驅(qū)動(dòng)主要包含了I2C?適配器數(shù)據(jù)結(jié)構(gòu)I2C_adapter,?以及描述在具體I2C?適配器上的總線通信方法i2c_algorithm?數(shù)據(jù)結(jié)構(gòu)。
(3)I2C?設(shè)備驅(qū)動(dòng):I2C?設(shè)備驅(qū)動(dòng)是對(duì)I2C?硬件體系結(jié)構(gòu)中設(shè)備端的實(shí)現(xiàn),?設(shè)備一般掛接在受CPU?控制的I2C?適配器上,?通過I2C?適配器與CPU?交換數(shù)據(jù)。I2C?設(shè)備驅(qū)動(dòng)主要包含了數(shù)據(jù)結(jié)構(gòu)i2c_driver?和i2c_client。
這三部分的關(guān)系如圖1?所示。
圖1Linux?中I2C?體系結(jié)構(gòu)
2.2?I2C驅(qū)動(dòng)程序中的重要數(shù)據(jù)結(jié)構(gòu)
在I2C?框架的i2c.h?這個(gè)頭文件中對(duì)4?個(gè)關(guān)鍵的結(jié)構(gòu)體進(jìn)行了定義,?它們分別是i2c_adapter、i2c_algorithm、i2c_driver?和i2c_client。結(jié)構(gòu)體i2c_adapter?是一個(gè)I2C控制器的邏輯抽象,并且作為最核心的數(shù)據(jù)結(jié)構(gòu)提供了I2C適配器的驅(qū)動(dòng)。i2c_algorithm對(duì)應(yīng)一套通信方法,?其封裝了對(duì)一個(gè)I2C?控制器的讀寫操作,?并且提供的通信函數(shù)可以控制適配器上產(chǎn)生特定的訪問周期,這套通信方法由驅(qū)動(dòng)開發(fā)者來完成。i2c_driver?則是對(duì)應(yīng)于一套驅(qū)動(dòng)方法,用于輔助作用的數(shù)據(jù)結(jié)構(gòu),不對(duì)應(yīng)任何物理實(shí)體,僅是提供了I2C?設(shè)備i2c_client?的驅(qū)動(dòng)。而i2c_client?對(duì)應(yīng)于真實(shí)的物理設(shè)備,描述具體設(shè)備可能的私有數(shù)據(jù)結(jié)構(gòu)。
2.3I2C驅(qū)動(dòng)程序中重要數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系
對(duì)于上述的4?個(gè)結(jié)構(gòu)體來說,?其中的i2c_driver?和i2c_client?是與具體I2C?設(shè)備相關(guān)的,而i2c_adapter?和i2c_algorithm則共同構(gòu)成I2C?總線適配器驅(qū)動(dòng)。一個(gè)algorithm?可以適用于多個(gè)I2C?總線上的不同adapters,?但具體的每個(gè)adapter?只能對(duì)應(yīng)于一個(gè)algorithm。在i2c_adapter?數(shù)據(jù)結(jié)構(gòu)中設(shè)計(jì)了clients指針數(shù)組,?用于記錄該總線上每個(gè)設(shè)備的i2c_client?數(shù)據(jù)結(jié)構(gòu)。
另外,?定義內(nèi)核中全局靜態(tài)指針數(shù)組adapters?和drivers?分別記錄已注冊(cè)的I2C?適配器驅(qū)動(dòng)和I2C?設(shè)備驅(qū)動(dòng)程序。值得注意的是同一個(gè)i2c_adapter?中的不同的i2c_client?可能使用同一個(gè)i2c_driver,而分屬于不同i2c_adapter?中的兩個(gè)i2c_client?也可能使用同一個(gè)i2c_driver。
3?一個(gè)具體的I2C?設(shè)備驅(qū)動(dòng)程序的開發(fā)
AT24C08?是由ATMEL?公司出品的一款EEPROM?存儲(chǔ)器。
作為一個(gè)標(biāo)準(zhǔn)的I2C?設(shè)備AT24C08?有4?個(gè)塊存儲(chǔ)區(qū),?一個(gè)塊有256?個(gè)數(shù)據(jù)存儲(chǔ)單元,整個(gè)AT24C08?具有1024?個(gè)存儲(chǔ)單元。由于每個(gè)數(shù)據(jù)存儲(chǔ)單元可存1?字節(jié)的數(shù)據(jù),所以整塊AT24C08?的存儲(chǔ)能力為1KB。
3.1?I2C?設(shè)備驅(qū)動(dòng)程序的一般結(jié)構(gòu)及運(yùn)行流程圖
開發(fā)一個(gè)具體的I2C?設(shè)備驅(qū)動(dòng)需要一個(gè)完整、標(biāo)準(zhǔn)的結(jié)構(gòu),而該結(jié)構(gòu)的實(shí)現(xiàn)是通過編寫兩個(gè)方面的接口而完成的,?一個(gè)是用以掛接I2C?adapter?層來實(shí)現(xiàn)對(duì)I2C?總線及I2C設(shè)備具體的訪問方法,?即I2C?核心層的接口,?主要實(shí)現(xiàn)attach_adapter,detach_client,command?等接口函數(shù)。另一個(gè)是對(duì)用戶應(yīng)用層的接口,?提供用戶程序訪問I2C設(shè)備的接口,?包括實(shí)現(xiàn)open,release,read,write?以及ioctl?等標(biāo)準(zhǔn)文件操作的接口函數(shù)。下面將通過對(duì)核心層接口和應(yīng)用層接口的分析來說明I2C?設(shè)備驅(qū)動(dòng)程序的運(yùn)行機(jī)制。圖2?為I2C?設(shè)備驅(qū)動(dòng)程序運(yùn)行流程圖(圖中at?代表具體的設(shè)備AT24C08):
3.2?I2C?設(shè)備驅(qū)動(dòng)的I2C?核心層接口分析
如圖2?的用戶空間在通過insmod?命令加載設(shè)備驅(qū)動(dòng)程序時(shí),?設(shè)備驅(qū)動(dòng)將通過使用動(dòng)態(tài)模塊的方式加載并指向設(shè)備初始化函數(shù)at_init(),在初始化函數(shù)中使用register_chrdev()進(jìn)行字符型設(shè)備的注冊(cè),?并可以通過靜態(tài)和動(dòng)態(tài)兩種方法來申請(qǐng)注冊(cè)到系統(tǒng)中的設(shè)備號(hào)。另外將調(diào)用核心i2c?-core.c?中提供的i2c_add_driver()函數(shù)注冊(cè)由at_driver?數(shù)據(jù)結(jié)構(gòu)描述的驅(qū)動(dòng)方法,該數(shù)據(jù)結(jié)構(gòu)中完成了對(duì)驅(qū)動(dòng)程序的標(biāo)示,?并包含了兩個(gè)重要的成員函數(shù)at_attach_adapter()和at_detach_client()。
在i2c_add_driver?()?注冊(cè)at_driver?數(shù)據(jù)結(jié)構(gòu)后,at_attach_adapter()函數(shù)就會(huì)被自動(dòng)調(diào)用,其遍歷系統(tǒng)中的每個(gè)i2c?總線驅(qū)動(dòng),?探測(cè)想要訪問的設(shè)備,?連接符合i2c?driver?特定條件的i2c?adapter,并通過i2c?adapter?實(shí)現(xiàn)對(duì)I2C?總線及其設(shè)備的訪問。
而at_attach_adapter()的功能則是依靠調(diào)用i2c-core.c?核心中的i2c_probe()函數(shù)來實(shí)現(xiàn)的,通過i2c_probe()函數(shù)可以認(rèn)領(lǐng)adapter所指向的適配器上的所有合適的設(shè)備。設(shè)備可能使用的地址由addr_data?數(shù)組指出。通過設(shè)備地址每次檢測(cè)到新設(shè)備后,i2c_probe()將使用它的第三個(gè)參數(shù)即回調(diào)函數(shù)初始化設(shè)備的數(shù)據(jù)結(jié)構(gòu)i2c_client,并用i2c_check_functionality()確定I2C?適配器所支持的通信方法。另外再使用i2c_attach_client()知會(huì)I2C?核心系統(tǒng)中包含了一個(gè)新的I2C?設(shè)備。
通過rmmod?命令對(duì)設(shè)備驅(qū)動(dòng)進(jìn)行卸載時(shí),?在卸載函數(shù)at_exit()中將使用i2c_del_driver(),其調(diào)用會(huì)引起與數(shù)據(jù)結(jié)構(gòu)at_driver?關(guān)聯(lián)的每個(gè)i2c_client?與之解除關(guān)聯(lián),?隨后at_detach_client()函數(shù)也將因此而被調(diào)用,而at_detach_client()中的i2c_detach_client()又完成與i2c_attach_client()相反的過程,并使用kfree?釋放由client?所占的內(nèi)存。另外卸載函數(shù)at_exit()中還將使用unregister_chrdev()對(duì)字符型設(shè)備進(jìn)行注銷。
3.3I2C設(shè)備驅(qū)動(dòng)用戶應(yīng)用層接口分析
在注冊(cè)字符型設(shè)備時(shí),?設(shè)備驅(qū)動(dòng)中初始化了一個(gè)structfile_operations?文件操作結(jié)構(gòu)體變量用于鏈接字符設(shè)備驅(qū)動(dòng)程序和用戶應(yīng)用程序,在該結(jié)構(gòu)中定義了一組函數(shù)指針。系統(tǒng)就是通過這組函數(shù)指針對(duì)AT24C08?進(jìn)行具體的操作,系統(tǒng)首先通過設(shè)備文件的主設(shè)備號(hào)找到相應(yīng)的設(shè)備驅(qū)動(dòng)程序,?然后讀取這個(gè)數(shù)據(jù)結(jié)構(gòu)相應(yīng)的函數(shù)指針,找到相關(guān)的功能函數(shù),接著把控制權(quán)交給該函數(shù),從而就在上層屏蔽了設(shè)備驅(qū)動(dòng)的具體實(shí)現(xiàn)細(xì)節(jié),提供給用戶一個(gè)方便快捷的接口。該結(jié)構(gòu)中的at_open(),對(duì)應(yīng)于用戶應(yīng)用層的open()接口函數(shù),其通過mknod?創(chuàng)建的設(shè)備節(jié)點(diǎn)對(duì)設(shè)備文件進(jìn)行打開操作。而對(duì)應(yīng)用戶層release?()?接口函數(shù)的at_release?()?則負(fù)責(zé)設(shè)備文件的釋放操作。file_operations?中的at_ioctl()則主要是為用戶提供一些控制該AT24C08?的命令。對(duì)一塊具體設(shè)備進(jìn)行讀寫操作是編寫驅(qū)動(dòng)要達(dá)到目的,file_operations結(jié)構(gòu)體中所指向的讀寫函數(shù)at_read(),at_write()完成了對(duì)AT24C08?的寫入和讀出操作。
就寫函數(shù)而言,?在寫數(shù)據(jù)之前必須先輸入測(cè)試單元的起始地址,?然后再對(duì)寫入的數(shù)據(jù)分配相應(yīng)內(nèi)存,?然后使用copy_from_user?命令把從用戶空間獲得的數(shù)據(jù)拷貝到內(nèi)核空間,并構(gòu)造I2C?消息數(shù)據(jù),最終通過i2c-core.c?的i2c-transfer()函數(shù)進(jìn)行I2C消息數(shù)組的傳輸,而i2c_transfer()將指向總線驅(qū)動(dòng)中的算法i2c_algorithm?所對(duì)應(yīng)的具體適配器的master_xfer()方法,這樣就借助i2c-core.c?作為紐帶連接了設(shè)備驅(qū)動(dòng)和總線驅(qū)動(dòng),并完成了兩者之間的通信,其運(yùn)行流程如圖2?的內(nèi)核空間所示。
對(duì)于讀函數(shù)at_read(),同樣要對(duì)數(shù)據(jù)進(jìn)行內(nèi)存的分配,構(gòu)造I2C消息,傳輸I2C?消息以及轉(zhuǎn)換數(shù)據(jù)空間等。兩者的主要區(qū)別則體現(xiàn)在對(duì)I2C?消息的構(gòu)造上,在讀出數(shù)據(jù)之前,先要寫地址,根據(jù)寫入的地址來尋找將要讀出的數(shù)據(jù)的起始地址,?所以在讀函數(shù)中就需要構(gòu)造兩條I2C?消息,一條用于寫地址操作,另一條用于讀數(shù)據(jù)操作。另外在轉(zhuǎn)換數(shù)據(jù)空間時(shí),?讀函數(shù)將使用copy_to_user?把內(nèi)核空間的數(shù)據(jù)拷貝到用戶空間。
3.4?AT24C08?的單設(shè)備多驅(qū)動(dòng)的實(shí)現(xiàn)方式
單設(shè)備多驅(qū)動(dòng)是本文的一個(gè)創(chuàng)新點(diǎn)。設(shè)計(jì)中實(shí)現(xiàn)了分3?個(gè)設(shè)備驅(qū)動(dòng)一對(duì)1?塊AT24C08?進(jìn)行操作。設(shè)備驅(qū)動(dòng)1?對(duì)AT24C08的第1?個(gè)塊操作,設(shè)備驅(qū)動(dòng)2?對(duì)第2?個(gè)塊操作,設(shè)備驅(qū)動(dòng)3?對(duì)第3?和第4?個(gè)塊進(jìn)行操作。對(duì)塊的分開操作體現(xiàn)在對(duì)設(shè)備地址的探測(cè)上,由于保存設(shè)備地址信息的是二元數(shù)組addr_data,所以在多驅(qū)動(dòng)對(duì)單一的AT24C08?操作時(shí)就需要在該二元數(shù)組中指明每個(gè)設(shè)備驅(qū)動(dòng)程序所控制的設(shè)備地址。對(duì)于控制第1?個(gè)塊的設(shè)備驅(qū)動(dòng)1,通過數(shù)組normal_addr?指出要進(jìn)行操作的設(shè)備地址為0x50,如下所示:
static?unsigned?short?normal_addr[]={?0x50,I2C_CLIENT_END};
再通過其對(duì)數(shù)組addr_data?進(jìn)行初始化,?這樣,?設(shè)備驅(qū)動(dòng)1就能檢測(cè)到數(shù)組中所指出的AT24C08?的第1?個(gè)塊,而跳過其他的塊,?達(dá)到了只對(duì)單一特定塊操作的目的。對(duì)于設(shè)備驅(qū)動(dòng)2?來說,?只需把數(shù)組normal_addr?中地址改為AT24C08?的第2?個(gè)塊的地址0x51?即可。同理,對(duì)設(shè)備驅(qū)動(dòng)3,只需把normal_addr?中的單一地址改為兩個(gè)地址即可,如下所示:
static?unsigned?short?normal_addr?[]?=?{?0x52,0x53,?I2C_CLIENT_END};
這樣就可使設(shè)備驅(qū)動(dòng)只探測(cè)到后兩個(gè)塊,而跳過其他塊,以達(dá)到對(duì)單一AT24C08?中多個(gè)塊操作的目的。然后再用insmod命令加載編譯好的三個(gè).ko?驅(qū)動(dòng)模塊,?獲得3?個(gè)不同的設(shè)備號(hào)后,接著根據(jù)所獲得的設(shè)備號(hào)使用mknod?命令創(chuàng)建3?個(gè)不同的字符型設(shè)備節(jié)點(diǎn),?最后通過用戶層的3?個(gè)測(cè)試程序分別打開已創(chuàng)建的這3?個(gè)不同的設(shè)備節(jié)點(diǎn)就能分別對(duì)不同的塊進(jìn)行讀寫操作,至此就實(shí)現(xiàn)了單設(shè)備多驅(qū)動(dòng)的控制方式。
同樣除了分3?個(gè)驅(qū)動(dòng)外,?驅(qū)動(dòng)開發(fā)者也可以編寫4?個(gè)設(shè)備驅(qū)動(dòng)分別對(duì)每1?個(gè)塊進(jìn)行操作,?或者就只編寫1?個(gè)設(shè)備驅(qū)動(dòng)對(duì)4?個(gè)塊一起操作,也適用于綁定非連續(xù)塊進(jìn)行操作,比如用一個(gè)設(shè)備驅(qū)動(dòng)控制第1?和第3?個(gè)塊。總之驅(qū)動(dòng)開發(fā)人員可以根據(jù)不同的需要進(jìn)行不同的組合方式。
3.5?AT24C08?設(shè)備驅(qū)動(dòng)程序的驗(yàn)證與測(cè)試
設(shè)備驅(qū)動(dòng)程序的驗(yàn)證,?需要通過用戶層的測(cè)試程序來實(shí)現(xiàn),測(cè)試程序如下:
fd=open("/dev/at",?O_RDWR);?//打開設(shè)備文件,獲得設(shè)備文件的文件描述符。
scanf("%u",?&start_address);?//輸入測(cè)試單元起始地址。
write(fd,buf,sizeof(buf));?//把以頁(yè)寫入方式把輸入的16?個(gè)數(shù)據(jù)寫入內(nèi)核空間。
?
轉(zhuǎn)載于:https://www.cnblogs.com/yuzaipiaofei/archive/2012/01/04/4124296.html
總結(jié)
以上是生活随笔為你收集整理的嵌入式Linux中I2C设备驱动程序的研究与实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MATLAB2017深度学习工具箱总结
- 下一篇: MNIST手写数字识别【Matlab神经