《電子技術(shù)應(yīng)用》
您所在的位置:首頁(yè) > 模擬設(shè)計(jì) > 業(yè)界動(dòng)態(tài) > I2C干貨-基于Cortex-A9(重新整理)

I2C干貨-基于Cortex-A9(重新整理)

2022-10-04
來(lái)源:FPGA之家
關(guān)鍵詞: I2C Cortex-A9 三星

  本文基于三星Cortex-A9架構(gòu),,Exynos4412講解I2C原理,、以及基于I2C的mpu6050陀螺儀的數(shù)據(jù)讀取實(shí)例(包括在裸機(jī)模式下數(shù)據(jù)的讀取以及基于Linux驅(qū)動(dòng)的讀取)。還會(huì)分析Linux內(nèi)核I2C架構(gòu),,篇幅過(guò)長(zhǎng),,絕對(duì)干貨,。

  裸機(jī)篇

  本篇首先詳細(xì)講解I2C時(shí)序,,然后講解如何基于三星I2C控制實(shí)現(xiàn)裸機(jī)讀取從設(shè)備信息方法。

  前言

  I2C(Inter-Integrated Circuit)總線(也稱 IIC 或 I2C) 是有PHILIPS公司開(kāi)發(fā)的兩線式串行總線,,用于連接微控制器及外圍設(shè)備,,是微電子通信控制領(lǐng)域廣泛采用的一種總線標(biāo)準(zhǔn)。它是同步通信的一種特殊形式,,具有接口線少,、控制方式簡(jiǎn)單、器件封裝形式小,、通信速率較高等優(yōu)點(diǎn),。

  

1.png

  Exynos4412 i2c控制器綜述

  Exynos4412精簡(jiǎn)指令集微處理器支持4個(gè)IIC總線控制器。為了能使連接在總線上的主和從設(shè)備之間傳輸數(shù)據(jù),,專(zhuān)用的數(shù)據(jù)線SDA和時(shí)鐘信號(hào)線SCL被使用,,他們都是雙向的。

  如果工作在多主機(jī)的IIC總線模式,,多個(gè)4412處理器將從從機(jī)那接收數(shù)據(jù)或發(fā)送數(shù)據(jù)給從機(jī),。在IIC總線上的主機(jī)端4412會(huì)啟動(dòng)或終止一個(gè)數(shù)據(jù)傳輸。4412的IIC總線控制器會(huì)用一個(gè)標(biāo)準(zhǔn)的IIC總線仲裁機(jī)制去實(shí)現(xiàn)多主機(jī)和多從機(jī)傳輸數(shù)據(jù),。

  通過(guò)控制如下寄存器以實(shí)現(xiàn)IIC總線上的多主機(jī)操作:

  控制寄存器:                   I2CCON

  狀態(tài)寄存器:                   I2CSTAT

  Tx/Rx數(shù)據(jù)偏移寄存器:  I2CDS

  地址寄存器:                   I2CADD

  如果I2C總線空閑,那么SCL和SDA信號(hào)線將都為高電平,。在SCL為高電平期間,,如果SDA有由高到低電平的跳變,那么將啟動(dòng)一個(gè)起始信號(hào),,如果SDA有由低到高電平的跳變,,將啟動(dòng)一個(gè)結(jié)束信號(hào),。

  主機(jī)端的設(shè)備總是提供起始和停止信號(hào)的一端。在起始信號(hào)被發(fā)出后,,一個(gè)數(shù)據(jù)字節(jié)的前7位被當(dāng)作地址通過(guò)SDA線被傳輸,。這個(gè)地制值決定了總線上的主設(shè)備將要選擇那個(gè)從設(shè)備作為傳輸對(duì)象,bit8決定傳輸數(shù)據(jù)的方向(是讀還是寫(xiě)),。

  I2C總線上的數(shù)據(jù)(即在SDA上傳輸?shù)臄?shù)據(jù))都是以8位字節(jié)傳輸?shù)?,在總線上傳輸操作的過(guò)程中,對(duì)發(fā)送或接收的數(shù)據(jù)字節(jié)數(shù)是沒(méi)有限制的,。I2C總線上的主/從設(shè)備發(fā)送數(shù)據(jù)總是以一個(gè)數(shù)據(jù)的最高位開(kāi)始傳輸(即MSB方式),,傳輸完一個(gè)字節(jié)后,應(yīng)答信號(hào)緊接其后,。

  Exynos4412 I2C總線接口特性

  共有9個(gè)通道,,支持多主、從I2C總線接口,。其中8個(gè)通道作為普通接口(即I2C0,、I2C1…),1個(gè)通道作為HDMI的專(zhuān)用接口,。

  7位地址模式,。

  串行,8位單向或雙向的數(shù)據(jù)傳輸,。

  在標(biāo)準(zhǔn)模式中,,每秒最多可以傳輸100k位,即12.5kB的數(shù)據(jù)量,。

  在快速模式中,,每秒最多可以傳輸400k位,即50kB的數(shù)據(jù)量,。

  支持主機(jī)端發(fā)送,、接收,從機(jī)端發(fā)送,、接收操作,。

  支持中斷和查詢方式。

  框圖

  

2.png

  從上圖可以看出,,4412提供4個(gè)寄存器來(lái)完成所有的IIC操作,。SDA線上的數(shù)據(jù)從IICDS寄存器經(jīng)過(guò)移位寄存器發(fā)出,或通過(guò)移位寄存器傳入IICDS寄器,;IICADD寄存器中保存4412當(dāng)做從機(jī)時(shí)的地址,;IICCON、IICSTAT兩個(gè)寄存器用來(lái)控制或標(biāo)識(shí)各種狀態(tài),比如選擇工作工作模式,,發(fā)出S信號(hào),、P信號(hào),決定是否發(fā)出ACK信號(hào),,檢測(cè)是否接收到ACK信號(hào),。

  I2C總線接口操作

  針對(duì)4412處理器的I2C總線接口,具備4種操作模式:

  主機(jī)發(fā)送模式

  主機(jī)接收模式

  從機(jī)發(fā)送模式

  從機(jī)接收模式

  下面將描述這些操作模式之間的功能關(guān)系:

  0,、數(shù)據(jù)有效性

  

3.png

  SDA線上的數(shù)據(jù)必須在時(shí)鐘的高電平周期保持穩(wěn)定,。數(shù)據(jù)線的高或低電平狀態(tài)IIC位傳輸數(shù)據(jù)的有效性在SCL線的時(shí)鐘信號(hào)是低電平才能改變。

  1.  開(kāi)始和停止條件

  當(dāng)4412的I2C接口空閑時(shí),,它往往工作在從機(jī)模式,。或者說(shuō),,4412的的i2c接口在SDA線上察覺(jué)到一個(gè)起始信號(hào)之前它應(yīng)該工作在從機(jī)模式,。當(dāng)控制器改變4412的i2c接口的工作模式為主機(jī)模式后,SDA線上發(fā)起數(shù)據(jù)傳輸并且控制器會(huì)產(chǎn)生SCL時(shí)鐘信號(hào),。

  開(kāi)始條件通過(guò)SDA線進(jìn)行串行的字節(jié)傳輸,,一個(gè)停止信號(hào)終止數(shù)據(jù)傳輸,停止信號(hào)是指SCL在高電平器件SDA線有從低到高電平的跳變,,主機(jī)端產(chǎn)生起始和停止條件,。當(dāng)主、從設(shè)備產(chǎn)生一個(gè)起始信號(hào)后,,I2C總線將進(jìn)入忙狀態(tài),。這里需要說(shuō)明的是上述主從設(shè)備都有可能作為主機(jī)端。

  當(dāng)一個(gè)主機(jī)發(fā)送了一個(gè)起始信號(hào)后,,它也應(yīng)該發(fā)送一個(gè)從機(jī)地址以通知總線上的從設(shè)備,。這個(gè)地址字節(jié)的低7位表示從設(shè)備地址,最高位表示傳輸數(shù)據(jù)的方向,,即主機(jī)將要進(jìn)行讀還是寫(xiě),。當(dāng)最高位是0時(shí),它將發(fā)起一個(gè)寫(xiě)操作(發(fā)送操作),;當(dāng)最高位是1時(shí),,它將發(fā)起一個(gè)讀數(shù)據(jù)的請(qǐng)求(接收操作)。

  主機(jī)端發(fā)起一個(gè)結(jié)束信號(hào)以完成傳輸操作,,如果主機(jī)端想在總線上繼續(xù)進(jìn)行數(shù)據(jù)的傳輸,,它將發(fā)出另外一個(gè)起始信號(hào)和從設(shè)備地址。用這樣的方式,,它們可以用各種各樣的格式進(jìn)行讀寫(xiě)操作,。

  下圖為起始和停止信號(hào):

  

4.png

  2.  數(shù)據(jù)傳輸格式

  放到SDA線上的所有字節(jié)數(shù)據(jù)的長(zhǎng)度應(yīng)該為8位,在每次傳輸數(shù)據(jù)時(shí),對(duì)傳輸數(shù)據(jù)量沒(méi)有限制,。在起始信號(hào)后的第一個(gè)數(shù)據(jù)字節(jié)應(yīng)該包含地址字段,當(dāng)4412的I2C接口被設(shè)置為主模式時(shí),,地址字節(jié)應(yīng)該由控制器端發(fā)出,。在每個(gè)字節(jié)后,應(yīng)該有一個(gè)應(yīng)答位,。

  如果從機(jī)要完成一些其他功能后(例如一個(gè)內(nèi)部中斷服務(wù)程序)才能繼續(xù)接收或發(fā)送下一個(gè)字節(jié),,從機(jī)可以拉低SCL迫使主機(jī)進(jìn)入等待狀態(tài)。當(dāng)從機(jī)準(zhǔn)備好接收下一個(gè)數(shù)據(jù)并釋放SCL后,,數(shù)據(jù)傳輸繼續(xù),。如果主機(jī)在傳輸數(shù)據(jù)期間也需要完成一些其他功能(例如一個(gè)內(nèi)部中斷服務(wù)程序)也可以拉低SCL以占住總線。

  下面的圖中將說(shuō)明數(shù)據(jù)傳輸格式:

  

5.png

  上圖中說(shuō)明,,在傳輸完每個(gè)字節(jié)數(shù)據(jù)后,,都會(huì)有一個(gè)應(yīng)答信號(hào),這個(gè)應(yīng)答信號(hào)在第9個(gè)時(shí)鐘周期,。具體過(guò)程如下(注意下面描述的讀寫(xiě)過(guò)程都是針對(duì) 4412處理器而言,,當(dāng)有具體的I2C設(shè)備與4412相連時(shí),數(shù)據(jù)表示什么需要看具體的I2C設(shè)備,,4412是不知道數(shù)據(jù)的含義的):

  寫(xiě)過(guò)程:主機(jī)發(fā)送一個(gè)起始信號(hào)S→發(fā)送從機(jī)7位地址和1位方向,,方向位表示寫(xiě)→主機(jī)釋放SDA線方便從機(jī)給回應(yīng)→有從機(jī)匹配到地址,拉低SDA線作為ACK→主機(jī)重新獲得SDA傳輸8位數(shù)據(jù)→主機(jī)釋放SDA線方便從機(jī)給回應(yīng)→從機(jī)收到數(shù)據(jù)拉低SDA線作為ACK告訴主機(jī)數(shù)據(jù)接收成功→主機(jī)發(fā)出停止信號(hào),。

  讀過(guò)程:主機(jī)發(fā)送一個(gè)起始信號(hào)S→發(fā)送從機(jī)7位地址和1位方向,,方向位表示讀→主機(jī)釋放SDA線方便從機(jī)給回應(yīng)→有從機(jī)匹配到地址,拉低SDA線作為ACK→從機(jī)繼續(xù)占用SDA線,,用SDA傳輸8位數(shù)據(jù)給主機(jī)→從機(jī)釋放SDA線(拉高)方便主機(jī)給回應(yīng)→主機(jī)接收到數(shù)據(jù)→主機(jī)獲得SDA線控制并拉低SDA線作為ACK告訴從機(jī)數(shù)據(jù)接收成功→主機(jī)發(fā)出停止信號(hào),。

  注意:在具體的I2C通信時(shí),要看I2C設(shè)備才能確定讀寫(xiě)時(shí)序,,比如下面即將描述的第七大點(diǎn)中的示例,,讀寫(xiě)EEPROM中就會(huì)說(shuō)道具體的數(shù)據(jù)含義,讀寫(xiě)過(guò)程,。

  3. 應(yīng)答信號(hào)的傳輸

  為了完成一個(gè)字節(jié)數(shù)據(jù)的傳輸,,接收方將發(fā)送一個(gè)應(yīng)答位給發(fā)送方。應(yīng)答信號(hào)出現(xiàn)在SCL線上的時(shí)鐘周期中的第九個(gè)時(shí)鐘周期,,為了發(fā)送或接收1個(gè)字節(jié)的數(shù)據(jù),,主機(jī)端會(huì)產(chǎn)生8個(gè)時(shí)鐘周期,為了傳輸一個(gè)ACK位,,主機(jī)端需要產(chǎn)生一個(gè)時(shí)鐘脈沖,。

  ACK時(shí)鐘脈沖到來(lái)之際,發(fā)送方會(huì)在SDA線上設(shè)置高電平以釋放SDA線。在ACK時(shí)鐘脈沖之間,,接收方會(huì)驅(qū)動(dòng)和保持SDA線為低電平,,這發(fā)生在第9個(gè)時(shí)鐘脈沖為高電平期間。應(yīng)答信號(hào)為低電平時(shí),,規(guī)定為有效應(yīng)答位(ACK簡(jiǎn)稱應(yīng)答位),,表示接收器已經(jīng)成功地接收了該字節(jié);應(yīng)答信號(hào)為高電平時(shí),,規(guī)定為非應(yīng)答位(NACK),,一般表示接收器接收該字節(jié)沒(méi)有成功。對(duì)于反饋有效應(yīng)答位ACK的要求是,,接收器在第9個(gè)時(shí)鐘脈沖之前的低電平期間將SDA線拉低,,并且確保在該時(shí)鐘的高電平期間為穩(wěn)定的低電平。如果接收器是主控器,,則在它收到最后一個(gè)字節(jié)后,,發(fā)送一個(gè)NACK信號(hào)(即不發(fā)出ACK信號(hào)),以通知被控發(fā)送器結(jié)束數(shù)據(jù)發(fā)送,,并釋放SDA線,,以便主控接收器發(fā)送一個(gè)停止信號(hào)P。

  

6.png

  4. 讀寫(xiě)操作

  當(dāng)I2C控制器在發(fā)送模式下發(fā)送數(shù)據(jù)后,,I2C總線接口將等待直到移位寄存器(I2CDS)接收到一個(gè)數(shù)據(jù),。在往此寄存器寫(xiě)入一個(gè)新數(shù)據(jù)前,SCL線應(yīng)該保持為低電平,,寫(xiě)完數(shù)據(jù)后,,I2C控制器將釋放SCL線。當(dāng)前正在傳輸?shù)臄?shù)據(jù)傳輸完成后,,4412會(huì)捕捉到一個(gè)中斷,,然后cpu將開(kāi)始往I2CDS寄存器中寫(xiě)入一個(gè)新的數(shù)據(jù)。

  當(dāng)I2C控制器在接收模式下接收到數(shù)據(jù)后,,I2C總線接口將等待直到I2CDS寄存器被讀,。在讀到新數(shù)據(jù)之前,SCL線會(huì)被保持為低電平,,讀到數(shù)據(jù)后I2C控制器將釋放掉SCL線,。一個(gè)新數(shù)據(jù)接收完成后,4412將收到一個(gè)中斷,,cpu收到這個(gè)中斷請(qǐng)求后,,它將從I2CDS寄存器中讀取數(shù)據(jù)。

  5. 總線仲裁機(jī)制

  總線上可能掛接有多個(gè)器件,,有時(shí)會(huì)發(fā)生兩個(gè)或多個(gè)主器件同時(shí)想占用總線的情況,,這種情況叫做總線競(jìng)爭(zhēng),。I2C總線具有多主控能力,可以對(duì)發(fā)生在SDA線上的總線競(jìng)爭(zhēng)進(jìn)行仲裁,,其仲裁原則是這樣的:當(dāng)多個(gè)主器件同時(shí)想占用總線時(shí),,如果某個(gè)主器件發(fā)送高電平,而另一個(gè)主器件發(fā)送低電平,,則發(fā)送電平與此時(shí)SDA總線電平不符的那個(gè)器件將自動(dòng)關(guān)閉其輸出級(jí),。總線競(jìng)爭(zhēng)的仲裁是在兩個(gè)層次上進(jìn)行的,。首先是地址位的比較,如果主器件尋址同一個(gè)從器件,,則進(jìn)入數(shù)據(jù)位的比較,,從而確保了競(jìng)爭(zhēng)仲裁的可靠性。由于是利用I2C總線上的信息進(jìn)行仲裁,,因此不會(huì)造成信息的丟失,。

  6. 終止條件

  當(dāng)一個(gè)從接收者不能識(shí)別從地址時(shí),它將保持SDA線為高電平,。在這樣的情況下,,主機(jī)會(huì)產(chǎn)生一個(gè)停止信號(hào)并且取消數(shù)據(jù)的傳輸。當(dāng)終止傳輸產(chǎn)生后,,主機(jī)端接收器會(huì)通過(guò)取消ACK的產(chǎn)生以告訴從機(jī)端發(fā)送器結(jié)束發(fā)送操作,。這將在主機(jī)端接收器接收到從機(jī)端發(fā)送器發(fā)送的最后一個(gè)字節(jié)之后發(fā)生,為了讓主機(jī)端產(chǎn)生一個(gè)停止條件,,從機(jī)端發(fā)送者將釋放SDA線,。

  7. 配置I2C總線

  如果要設(shè)置I2C總線中SCL時(shí)鐘信號(hào)的頻率,可以在I2CCON寄存器中設(shè)置4位分頻器的值,。I2C總線接口地址值存放在I2C總線地址寄存器(I2CADD)中,,默認(rèn)值未知。

  8. 每種模式下的操作流程圖

  在I2C總線上執(zhí)行任何的收發(fā)Tx/Rx操作前,,應(yīng)該做如下配置:

 ?。?)在I2CADD寄存器中寫(xiě)入從設(shè)備地址

  (2)設(shè)置I2CCON控制寄存器

  a. 使能中斷

  b. 定義SCL頻率

 ?。?)設(shè)置I2CSTAT寄存器以使能串行輸出

  下圖為主設(shè)備發(fā)送模式

  

7.png

  下圖為主設(shè)備接收模式

  

8.png

  下圖為從設(shè)備發(fā)送模式

 

9.png

  下圖為從設(shè)備接收

  

10.png

  I2C控制器寄存器

  I2C控制器用到的寄存器如下所示:

  

11.png

  1-- I2C總線控制寄存器

  IICCON寄存器用于控制是否發(fā)出ACK信號(hào),、設(shè)置發(fā)送器的時(shí)鐘、開(kāi)啟I2C中斷,,并標(biāo)識(shí)中斷是否發(fā)生

  

12.png

  使用IICCON寄存器時(shí),,有如下注意事項(xiàng)

  發(fā)送模式的時(shí)鐘頻率由位[6]、位[3:0]聯(lián)合決定,。另外,,當(dāng)          IICCON[6]=0時(shí),,IICCON[3:0]不能取0或1。

  位[4]用來(lái)標(biāo)識(shí)是否有I2C中斷發(fā)生,,讀出為0時(shí)標(biāo)識(shí)沒(méi)有中斷發(fā)生,,讀出為1時(shí)標(biāo)識(shí)有中斷發(fā)生。當(dāng)此位為1時(shí),,SCL線被拉低,,此時(shí)所以I2C傳輸停止;如果要繼續(xù)傳輸,,需寫(xiě)入0清除它,。

  中斷在以下3種情況下發(fā)生:

  1 -- 當(dāng)發(fā)送地址信息或接收到一個(gè)從機(jī)地址并且吻合時(shí);

  2 -- 當(dāng)總線仲裁失敗時(shí),;

  3 -- 當(dāng)發(fā)送/接收完一個(gè)字節(jié)的數(shù)據(jù)(包括響應(yīng)位)時(shí),;

  基于SDA、SCL線上時(shí)間特性的考慮,,要發(fā)送數(shù)據(jù)時(shí),,先將數(shù)據(jù)寫(xiě)入IICDS寄存器,然后再清除中斷,。

  如果IICCON[5]=0,,IICCON[4]將不能正常工作,所以,,即使不使用I2C中斷,,也要將IICCON[5]設(shè)為1.

  2 -- I2C狀態(tài)寄存器

  IICSTAT寄存器用于選擇I2C接口的工作模式,發(fā)出S信號(hào),、P信號(hào),,使能接收/發(fā)送功能,并標(biāo)識(shí)各種狀態(tài),,比如總線仲裁是否成功,、作為從機(jī)時(shí)是否被尋址、是否接收到0地址,、是否接收到ACK信號(hào)等,。

  

13.png

  3 -- I2C數(shù)據(jù)發(fā)送/接收移位寄存器

  

14.png

  fs4412的i2c總線上掛載了mpu6050

  mpu6050每次讀取或者要寫(xiě)入數(shù)據(jù)時(shí),必須先告知從設(shè)備要操作的內(nèi)部寄存器地址(RA),,然后緊跟著讀取或者寫(xiě)入數(shù)據(jù)(DATA),,內(nèi)部寄存器的配置和讀取一次最多1個(gè)data,交互時(shí)序如下:

  

15.png

  【注意】上述兩個(gè)時(shí)序非常重要,,下面我們編寫(xiě)基于linux的驅(qū)動(dòng)編寫(xiě)i2c_msg還要再依賴他,。

  上述簡(jiǎn)化時(shí)序的術(shù)語(yǔ)解釋如下

  

16.png

  【寄存器使用規(guī)則】

  下面先提前講一下具體應(yīng)用中如何啟動(dòng)和恢復(fù)IIC的傳輸

  啟動(dòng)或恢復(fù)4412的I2C傳輸有以下兩種方法。

  1) 當(dāng)IICCON[4]即中斷狀態(tài)位為0時(shí),,通過(guò)寫(xiě)IICSTAT寄存器啟動(dòng)I2C操作,。有以下兩種情況,。

  1--在主機(jī)模式,

  令I(lǐng)ICSTAT[5:4]等于0b11,,將發(fā)出S信號(hào)和IICDS寄存器的數(shù)據(jù)(尋址),,

  令I(lǐng)ICSTAT[5:4]等于0b01,將發(fā)出P信號(hào),。

  2--在從機(jī)模式,,令I(lǐng)ICSTAT[4]等于1將等待其他主機(jī)發(fā)出S信號(hào)及地址信息。

  2)當(dāng)IICCON[4]即中斷狀態(tài)為1時(shí),,表示I2C操作被暫停,。在這期間設(shè)置好其他寄存器之后,向IICCON[4]寫(xiě)入0即可恢復(fù)I2C操作,。所謂“設(shè)置其他寄存器”,,有以下三種情況:

  1--對(duì)于主機(jī)模式,可以按照上面1的方法寫(xiě)IICSTAT寄存器,,恢復(fù)I2C操作后即可發(fā)出S信號(hào)和IICDS寄存器的值(尋址),或發(fā)出P信號(hào),。

  2--對(duì)于發(fā)送器,,可以將下一個(gè)要發(fā)送的數(shù)據(jù)寫(xiě)入IICDS寄存器中,恢復(fù)I2C操作后即可發(fā)出這個(gè)數(shù)據(jù),。

  3--對(duì)于接收器,,可以從IICDS寄存器讀出接收到的數(shù)據(jù)。最后向IICCON[4]寫(xiě)入0的同時(shí),,設(shè)置IICCON[7]以決定是否在接收到下一個(gè)數(shù)據(jù)后是否發(fā)出ACK信號(hào),。

  MPU6050

  MPU-6000(6050)為全球首例整合性6軸運(yùn)動(dòng)處理組件,相較于多組件方案,,免除了組合陀螺儀與加速器時(shí)間軸之差的問(wèn)題,,減少了大量的封裝空間。當(dāng)連接到三軸磁強(qiáng)計(jì)時(shí),,MPU-60X0提供完整的9軸運(yùn)動(dòng)融合輸出到其主I2C或SPI端口(SPI僅在MPU-6000上可用),。

  

17.png

  MPU-6000(6050)的角速度全格感測(cè)范圍為±250、±500,、±1000與±2000°/sec (dps),,可準(zhǔn)確追蹤快速與慢速動(dòng)作,并且,,用戶可程式控制的加速器全格感測(cè)范圍為±2g,、±4g±8g與±16g。產(chǎn)品傳輸可透過(guò)最高至400kHz的IIC或最高達(dá)20MHz的SPI(MPU-6050沒(méi)有SPI),。

  電路圖

  【MPU6050硬件電路圖】(實(shí)際板子電路圖不一定和下面一樣,,具體問(wèn)題具體分析,,本例參考exynos-fs4412開(kāi)發(fā)板)

  

18.png

  1 AD0接地的  值為 0

  

19.png

  所以從設(shè)備地址為0x68;

  2 SCL,、SDA連接的i2c_SCL5,、i2c_SDA5

  

20.png

  由此可得這兩個(gè)信號(hào)線復(fù)用了GPIO的GPB的2、3引腳,;

  3 查閱exynos4412 datasheet 6.2.2 Part 1可得

 

21.png

  所以設(shè)置GPIO 的 GPB     【15:8】= 0x33 即可,。

  MPU6050內(nèi)部寄存器

  mpu6050內(nèi)部寄存器的使用,參考datasheet《MPU-6000 and MPU-6050

  Register Map and Descriptions Revision 4.0 》,。

  Mpu6050內(nèi)部有100多個(gè)寄存器,。比如:

  

22.png

  這個(gè)寄存器是用來(lái)設(shè)置加速度屬性的,當(dāng)bit[4:3] 設(shè)置為0,,表示3個(gè)軸的加速度量程最大為±2g,。

  mpu6050的內(nèi)部寄存器非常多,并不需要每一個(gè)寄存器都需要搞懂,,在如下代碼實(shí)例中,,我已經(jīng)列舉出常用的寄存器以及他們的典型值,其他的寄存器不再一一介紹,。

  下面是個(gè)IIC總線實(shí)例:

  用IIC總線實(shí)現(xiàn)CPU與MPU-6050的數(shù)據(jù)查詢

  具體代碼如下:

  //****************************************

  // MPU6050常用內(nèi)部地址,,以下地址在mpu6050內(nèi)部

  //****************************************

  #define SMPLRT_DIV  0x19 //陀螺儀采樣率,典型值:0x07(125Hz)

  #define CONFIG   0x1A //低通濾波頻率,,典型值:0x06(5Hz)

  #define GYRO_CONFIG  0x1B //陀螺儀自檢及測(cè)量范圍,,典型值:0x18(不自檢,2000deg/s)

  #define ACCEL_CONFIG 0x1C //加速計(jì)自檢,、測(cè)量范圍及高通濾波頻率,,典型值:0x01(不自檢,2G,,5Hz)

  #define ACCEL_XOUT_H 0x3B

  #define ACCEL_XOUT_L 0x3C

  #define ACCEL_YOUT_H 0x3D

  #define ACCEL_YOUT_L 0x3E

  #define ACCEL_ZOUT_H 0x3F

  #define ACCEL_ZOUT_L 0x40

  #define TEMP_OUT_H  0x41

  #define TEMP_OUT_L  0x42

  #define GYRO_XOUT_H  0x43

  #define GYRO_XOUT_L  0x44

  #define GYRO_YOUT_H  0x45

  #define GYRO_YOUT_L  0x46

  #define GYRO_ZOUT_H  0x47

  #define GYRO_ZOUT_L  0x48

  #define PWR_MGMT_1  0x6B //電源管理,,典型值:0x00(正常啟用)

  #define WHO_AM_I  0x75 //IIC地址寄存器(默認(rèn)數(shù)值0x68,只讀)

  #define SlaveAddress 0xD0 //IIC寫(xiě)入時(shí)的地址字節(jié)數(shù)據(jù),,+1為讀取

  typedef struct {

  unsigned int CON;

  unsigned int DAT;

  unsigned int PUD;

  unsigned int DRV;

  unsigned int CONPDN;

  unsigned int PUDPDN;

  }gpb;

  #define GPB (* (volatile gpb *)0x11400040)

  typedef struct {

  unsigned int I2CCON;

  unsigned int I2CSTAT;

  unsigned int I2CADD;

  unsigned int I2CDS;

  unsigned int I2CLC;

  }i2c5;

  #define  I2C5 (* (volatile i2c5 *)0x138B0000 )

  void mydelay_ms(int time)

  {

  int i, j;

  while(time--)

  {

  for (i = 0; i < 5; i++)

  for (j = 0; j < 514; j++),;

  }

  }

  /**********************************************************************

  * @brief            iic read a byte program body

  * @param[in]    slave_addr, addr, &data

  * @return         None

  **********************************************************************/

  void iic_read(unsigned char slave_addr, unsigned char addr, unsigned char *data)

  {

  /*根據(jù)mpu6050的datasheet,要讀取數(shù)據(jù)必須先執(zhí)行寫(xiě)操作:寫(xiě)入一個(gè)從設(shè)備地址,,

  然后執(zhí)行讀操作,,才能讀取到該內(nèi)部寄存器的內(nèi)容*/

  I2C5.I2CDS = slave_addr; //將從機(jī)地址寫(xiě)入I2CDS寄存器中

  I2C5.I2CCON = (1 《 7)|(1 《 6)|(1 《 5); //設(shè)置時(shí)鐘并使能中斷

  I2C5.I2CSTAT = 0xf0;    //[7:6]設(shè)置為0b11,,主機(jī)發(fā)送模式,;

  //往[5:4]位寫(xiě)0b11,即產(chǎn)生啟動(dòng)信號(hào),,發(fā)出IICDS寄存器中的地址

  while(?。↖2C5.I2CCON & (1 《 4))),; // 等待傳輸結(jié)束,傳輸結(jié)束后,,I2CCON [4]位為1,,標(biāo)識(shí)有中斷發(fā)生;

  // 此位為1時(shí),,SCL線被拉低,,此時(shí)I2C傳輸停止;

  I2C5.I2CDS = addr;       //寫(xiě)命令值

  I2C5.I2CCON = I2C5.I2CCON & (~(1 《 4)),;// I2CCON [4]位清0,,繼續(xù)傳輸

  while(!(I2C5.I2CCON & (1 《 4))),;// 等待傳輸結(jié)束

  I2C5.I2CSTAT = 0xD0; // I2CSTAT[5:4]位寫(xiě)0b01,發(fā)出停止信號(hào)

  I2C5.I2CDS = slave_addr | 1;  //表示要讀出數(shù)據(jù)

  I2C5.I2CCON = (1 《 7)|(1 《 6) |(1 《 5) ; //設(shè)置時(shí)鐘并使能中斷

  I2C5.I2CSTAT = 0xb0;//[7:6]位0b10,主機(jī)接收模式,;

  //往[5:4]位寫(xiě)0b11,即產(chǎn)生啟動(dòng)信號(hào),,發(fā)出IICDS寄存器中的地址

  //    I2C5.I2CCON = I2C5.I2CCON & (~(1 《 4)),;    如果強(qiáng)行關(guān)閉,將讀取不到數(shù)據(jù)

  while(?。↖2C5.I2CCON & (1 《 4))),;//等待傳輸結(jié)束,接收數(shù)據(jù)

  I2C5.I2CCON &= ~((1《7)|(1 《 4)),;/* Resume the operation  & no ack*/

  // I2CCON [4]位清0,繼續(xù)傳輸,,接收數(shù)據(jù),,

  // 主機(jī)接收器接收到最后一字節(jié)數(shù)據(jù)后,不發(fā)出應(yīng)答信號(hào) no ack

  // 從機(jī)發(fā)送器釋放SDA線,,以允許主機(jī)發(fā)出P信號(hào),,停止傳輸;

  while(?。↖2C5.I2CCON & (1 《 4))),;// 等待傳輸結(jié)束

  I2C5.I2CSTAT = 0x90;

  *data = I2C5.I2CDS;

  I2C5.I2CCON &= ~(1《4);  /*clean interrupt pending bit  */

  mydelay_ms(10),;

  *data = I2C5.I2CDS;

  }

  /**************************************************************

  * @brief            iic write a byte program body

  * @param[in]    slave_addr, addr, data

  * @return         None

  *************************************************************/

  void iic_write (unsigned char slave_addr, unsigned char addr, unsigned char data)

  {

  I2C5.I2CDS = slave_addr;

  I2C5.I2CCON = (1 《 7)|(1 《 6)|(1 《 5) ;

  I2C5.I2CSTAT = 0xf0;

  while(?。↖2C5.I2CCON & (1 《 4)));

  I2C5.I2CDS = addr;

  I2C5.I2CCON = I2C5.I2CCON & (~(1 《 4)),;

  while(?。↖2C5.I2CCON & (1 《 4)));

  I2C5.I2CDS = data;

  I2C5.I2CCON = I2C5.I2CCON & (~(1 《 4)),;

  while(?。↖2C5.I2CCON & (1 《 4))),;

  I2C5.I2CSTAT = 0xd0;

  I2C5.I2CCON = I2C5.I2CCON & (~(1 《 4));

  mydelay_ms(10),;

  }

  void MPU6050_Init ()

  {

  iic_write(SlaveAddress, PWR_MGMT_1, 0x00),;

  iic_write(SlaveAddress, SMPLRT_DIV, 0x07);

  iic_write(SlaveAddress, CONFIG, 0x06),;

  iic_write(SlaveAddress, GYRO_CONFIG, 0x18),;

  iic_write(SlaveAddress, ACCEL_CONFIG, 0x01);

  }

  /*讀取mpu6050某個(gè)內(nèi)部寄存器的內(nèi)容*/

  int get_data(unsigned char addr)

  {

  char data_h, data_l;

  iic_read(SlaveAddress, addr, &data_h),;

  iic_read(SlaveAddress, addr+1, &data_l),;

  return (data_h《8)|data_l;

  }

  /*

  *  裸機(jī)代碼,不同于LINUX 應(yīng)用層,, 一定加循環(huán)控制

  */

  int main(void)

  {

  int data;

  unsigned char zvalue;

  GPB.CON = (GPB.CON & ~(0xff《8)) | 0x33《8; // GPBCON[3], I2C_5_SCL GPBCON[2], I2C_5_SDAmydelay_ms(100),;

  uart_init();

  /*---------------------------------------------------------------*/

  I2C5.I2CSTAT = 0xD0;

  I2C5.I2CCON &= ~(1《4),;  /*clean interrupt pending bit  */

  /*--------------------------------------------------------------*/

  mydelay_ms(100),;

  MPU6050_Init();

  mydelay_ms(100),;

  printf("\n********** I2C test!! ***********\n"),;

  while(1)

  {

  data = get_data(GYRO_ZOUT_H);

  printf(" GYRO --> Z <---:Hex: %x", data),;

  data = get_data(GYRO_XOUT_H),;

  printf(" GYRO --> X <---:Hex: %x", data);

  printf("\n"),;

  mydelay_ms(1000),;

  }

  return 0;

  }

  實(shí)驗(yàn)結(jié)果如下:

  ********** I2C test!! ***********

  GYRO --> Z <---:Hex: 1c GYRO --> X <---:Hex: feda

  GYRO --> Z <---:Hex: fefc GYRO --> X <---:Hex: fed6

  GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: fed6

  GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: fedc

  GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: feda

  GYRO --> Z <---:Hex: fefc GYRO --> X <---:Hex: fed6

  GYRO --> Z <---:Hex: fefe GYRO --> X <---:Hex: feda

  GYRO --> Z <---:Hex: fcf2 GYRO --> X <---:Hex: 202

  GYRO --> Z <---:Hex: ec GYRO --> X <---:Hex: faa0

  GYRO --> Z <---:Hex: 4c GYRO --> X <---:Hex: e

  GYRO --> Z <---:Hex: fe GYRO --> X <---:Hex: fed8

  GYRO --> Z <---:Hex: 0 GYRO --> X <---:Hex: fede

  GYRO --> Z <---:Hex: 0 GYRO --> X <---:Hex: feda

  讀寫(xiě)操作代碼解析:

  寫(xiě)入一個(gè)數(shù)據(jù)流程:

  

23.png

  讀數(shù)據(jù)流程:

  

24.png

  上圖閱讀注意點(diǎn):

  從設(shè)備地址是在用的時(shí)候應(yīng)該左移一位|讀寫(xiě)位,比如寫(xiě)reg=0x68<1|0,即0xD0;

  主設(shè)備發(fā)出S信號(hào),,需要將I2CSTATn 的bite:5設(shè)置為1,;

  主設(shè)備發(fā)出p信號(hào),需要將I2CSTATn 的bite:5設(shè)置為0,;

  主機(jī)發(fā)送數(shù)據(jù)需要將寄存器I2CCONn的bit:4置0,,to reume the operation;

  主機(jī)等待從設(shè)備發(fā)送的ack或者data,,需要輪訓(xùn)判斷I2CCONn的bit:4是否置1,;

  代碼的理解除了結(jié)合功能流程圖、時(shí)序圖,、源代碼還要結(jié)合寄存器說(shuō)明,;

  代碼的編寫(xiě)順序必須嚴(yán)格按照時(shí)序和模塊流程圖執(zhí)行;

  時(shí)序中的每一個(gè)數(shù)據(jù)信號(hào)(包括ack、data,、reg)的產(chǎn)生或者發(fā)送對(duì)應(yīng)的代碼都用箭頭以及相同的顏色框處,;

  對(duì)于read操作,NACK的回復(fù)需要在接收最后一個(gè)data之前設(shè)置I2CCONn :7位為0,,這樣在收到從設(shè)備的data后,,才會(huì)將SDA拉低。

  I2C Linux驅(qū)動(dòng)篇

  本篇講解mpu6050基于Linux的驅(qū)動(dòng)的實(shí)現(xiàn),。

  Linux I2C架構(gòu)

  Linux內(nèi)核已經(jīng)為我們編寫(xiě)好了I2C的架構(gòu),,從設(shè)備信息可以在內(nèi)核文件中直接寫(xiě)死,也可以通過(guò)設(shè)備樹(shù)來(lái)提供,,我們只需要實(shí)現(xiàn)i2c_driver,然后注冊(cè)到i2c架構(gòu)中即可,。

  i2c的內(nèi)核架構(gòu)源碼位于:

  \drivers\i2c

  I2C核心(i2c_core)

  I2C核心維護(hù)了i2c_bus結(jié)構(gòu)體,提供了I2C總線驅(qū)動(dòng)和設(shè)備驅(qū)動(dòng)的注冊(cè),、注銷(xiāo)方法,,維護(hù)了I2C總線的驅(qū)動(dòng)、設(shè)備鏈表,,實(shí)現(xiàn)了設(shè)備,、驅(qū)動(dòng)的匹配探測(cè)。此部分代碼由Linux內(nèi)核提供,。

  I2C總線驅(qū)動(dòng)

  I2C總線驅(qū)動(dòng)維護(hù)了I2C適配器數(shù)據(jù)結(jié)構(gòu)(i2c_adapter)和適配器的通信方法數(shù)據(jù)結(jié)構(gòu)(i2c_algorithm),。所以I2C總線驅(qū)動(dòng)可控制I2C適配器產(chǎn)生start、stop,、ACK等,。此部分代碼由具體的芯片廠商提供,比如Samsung,、高通,。

  I2C設(shè)備驅(qū)動(dòng)

  I2C設(shè)備驅(qū)動(dòng)主要維護(hù)兩個(gè)結(jié)構(gòu)體:i2c_driver和i2c_client,實(shí)現(xiàn)和用戶交互的文件操作集合fops,、cdev等,。此部分代碼就是驅(qū)動(dòng)開(kāi)發(fā)者需要完成的,。

  Linux內(nèi)核中描述I2C的四個(gè)核心結(jié)構(gòu)體

  1)i2c_client—掛在I2C總線上的I2C從設(shè)備

  每一個(gè)i2c從設(shè)備都需要用一個(gè)i2c_client結(jié)構(gòu)體來(lái)描述,,i2c_client對(duì)應(yīng)真實(shí)的i2c物理設(shè)備device。

  

25.png

  但是i2c_client不是我們自己寫(xiě)程序去創(chuàng)建的,,而是通過(guò)以下常用的方式自動(dòng)創(chuàng)建的:

  方法一: 分配,、設(shè)置、注冊(cè)i2c_board_info

  方法二: 獲取adapter調(diào)用i2c_new_device

  方法三: 通過(guò)設(shè)備樹(shù)(devicetree)創(chuàng)建

  方法1和方法2通過(guò)platform創(chuàng)建,,這兩種方法在內(nèi)核3.0版本以前使用所以在這不詳細(xì)介紹,;方法3是最新的方法,3.0版本之后的內(nèi)核都是通過(guò)這種方式創(chuàng)建的,文章后面的案例就按方法3,。

  2)i2c_adapter

  I2C總線適配器,,即soc中的I2C總線控制器,硬件上每一對(duì)I2C總線都對(duì)應(yīng)一個(gè)適配器來(lái)控制它,。在Linux內(nèi)核代碼中,,每一個(gè)adapter提供了一個(gè)描述它的結(jié)構(gòu)(struct i2c_adapter),再通過(guò)i2c core層將i2c設(shè)備與i2c adapter關(guān)聯(lián)起來(lái),。主要用來(lái)完成i2c總線控制器相關(guān)的數(shù)據(jù)通信,,此結(jié)構(gòu)體在芯片廠商提供的代碼中維護(hù)。

  

26.png

  3)i2c_algorithm

  I2C總線數(shù)據(jù)通信算法,,通過(guò)管理I2C總線控制器,,實(shí)現(xiàn)對(duì)I2C總線上數(shù)據(jù)的發(fā)送和接收等操作。亦可以理解為I2C總線控制器(適配器adapter)對(duì)應(yīng)的驅(qū)動(dòng)程序,,每一個(gè)適配器對(duì)應(yīng)一個(gè)驅(qū)動(dòng)程序,,用來(lái)描述適配器和設(shè)備之間的通信方法,由芯片廠商去實(shí)現(xiàn)的,。

  

27.png

  4)i2c_driver

  用于管理I2C的驅(qū)動(dòng)程序和i2c設(shè)備(client)的匹配探測(cè),,實(shí)現(xiàn)與應(yīng)用層交互的文件操作集合fops、cdev等,。

  

28.png

  設(shè)備樹(shù)

  1. 硬件電路圖如下:

  

29.png

  由上圖所示硬件使用的是I2C通道5,,

  2. 查找exnos4412的datasheet 29.6.1節(jié),對(duì)應(yīng)的基地址為0x138B0000,。

  

31.png

  3. 由上圖可知中斷引腳復(fù)用的是GPX3_3,。

  4. 在上一篇中,我們已經(jīng)得到mpu6050從設(shè)備地址為0x68,。

  linux內(nèi)核中三星已經(jīng)為I2C控制器和設(shè)備節(jié)點(diǎn)的編寫(xiě)提供了說(shuō)明手冊(cè):

  G:\linux-3.14-fs4412\Documentation\devicetree\bindings\i2c\i2c-s3c2410.txt

  該文檔提供了一個(gè)具體范例,,如下:

  Example:

  i2c@13870000 {

  compatible = "samsung,s3c2440-i2c";

  reg = <0x13870000 0x100>;

  interrupts = <345>;

  samsung,i2c-sda-delay = <100>;

  samsung,i2c-max-bus-freq = <100000>;

  /* Samsung GPIO variant begins here */

  gpios = <&gpd1 2 0 /* SDA */

  &gpd1 3 0 /* SCL */>;

  /* Samsung GPIO variant ends here */

  /* Pinctrl variant begins here */

  pinctrl-0 = <&i2c3_bus>;

  pinctrl-names = "default";

  /* Pinctrl variant ends here */

  #address-cells = <1>;

  #size-cells = <0>;

  wm8994@1a {

  compatible = "wlf,wm8994";

  reg = <0x1a>;

  };

  };

  注意:三星的exynos4412的i2c控制器驅(qū)動(dòng)仍然沿用了s3c2410的驅(qū)動(dòng)。

  綜上,,最終I2C設(shè)備樹(shù)節(jié)點(diǎn)編寫(xiě)如下:

  i2c@138B0000 {          基地址是 138B0000

  samsung,i2c-sda-delay = <100>;

  samsung,i2c-max-bus-freq = <20000>;

  pinctrl-0 =<&i2c5_bus>;            通道5

  pinctrl-names = "default";

  status = "okay";

  mpu6050-3-asix@68 {

  compatible = "invensense,mpu6050";

  reg= <0x68>;           從設(shè)備地址

  interrupt-parent = <&gpx3>;   中斷父節(jié)點(diǎn)

  interrupts= <3  2>;    中斷index=3,,中斷觸發(fā)方式:下降沿觸發(fā)

  };

  };

  其中 外面節(jié)點(diǎn) i2c@138B0000{}是i2c控制器設(shè)備樹(shù)信息,子節(jié)點(diǎn)

  mpu6050-3-asix@68{}是從設(shè)備mpu6050的設(shè)備樹(shù)節(jié)點(diǎn)信息,。

  【注意】關(guān)于設(shè)備樹(shù)的編譯燒錄,,本篇不做詳細(xì)說(shuō)明,后續(xù)會(huì)開(kāi)一篇詳細(xì)講述設(shè)備樹(shù)的使用,。

  結(jié)構(gòu)體之間關(guān)系如下:

  

32.png

  1. 設(shè)備樹(shù)節(jié)點(diǎn)分為控制器和從設(shè)備兩部分,,控制器節(jié)點(diǎn)信息會(huì)通過(guò)platform總線與控制器驅(qū)動(dòng)匹配,控制器驅(qū)動(dòng)已經(jīng)由內(nèi)核提供,,結(jié)構(gòu)體如下:

  static struct platform_driver s3c24xx_i2c_driver = {

  .probe    = s3c24xx_i2c_probe,

  .remove    = s3c24xx_i2c_remove,

  .id_table  = s3c24xx_driver_ids,

  .driver    = {

  .owner  = THIS_MODULE,

  .name  = "s3c-i2c",

  .pm  = S3C24XX_DEV_PM_OPS,

  .of_match_table = of_match_ptr(s3c24xx_i2c_match),,

  },

  };

  #ifdef CONFIG_OF

  static const struct of_device_id s3c24xx_i2c_match[] = {

  { .compatible = "samsung,s3c2410-i2c", .data = (void *)0 },

  { .compatible = "samsung,s3c2440-i2c", .data = (void *)QUIRK_S3C2440 },

  { .compatible = "samsung,s3c2440-hdmiphy-i2c",

  .data = (void *)(QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO) },

  { .compatible = "samsung,exynos5440-i2c",

  .data = (void *)(QUIRK_S3C2440 | QUIRK_NO_GPIO) },

  { .compatible = "samsung,exynos5-sata-phy-i2c",

  .data = (void *)(QUIRK_S3C2440 | QUIRK_POLL | QUIRK_NO_GPIO) },

  {},

  };

  MODULE_DEVICE_TABLE(of, s3c24xx_i2c_match);

  #endif

  2. 從設(shè)備節(jié)點(diǎn)信息最終會(huì)通過(guò)i2c_bus與i2c_driver匹配,i2c_driver需要由開(kāi)發(fā)者自己注冊(cè),,并實(shí)現(xiàn)字符設(shè)備接口和創(chuàng)建設(shè)備節(jié)點(diǎn)/dev/mpu6050,;

  3. 用戶通過(guò)字符設(shè)備節(jié)點(diǎn)/dev/mpu6050調(diào)用內(nèi)核的注冊(cè)的接口函數(shù)mpu6050_read_byte、mpu6050_write_byte,;

  4. 內(nèi)核的i2c core模塊提供了i2c協(xié)議相關(guān)的核心函數(shù),,在實(shí)現(xiàn)讀寫(xiě)操作的時(shí)候,需要通過(guò)一個(gè)重要的函數(shù)i2c_transfer(),,這個(gè)函數(shù)是i2c核心提供給設(shè)備驅(qū)動(dòng)的,,通過(guò)它發(fā)送的數(shù)據(jù)需要被打包成i2c_msg結(jié)構(gòu),這個(gè)函數(shù)最終會(huì)回調(diào)相應(yīng)i2c_adapter->i2c_algorithm->master_xfer()接口將i2c_msg對(duì)象發(fā)送到i2c物理控制器,。

  【注】實(shí)例所用soc是exynos4412,,為三星公司所出品,所以i2c控制器設(shè)備樹(shù)節(jié)點(diǎn)信息可以參考linux內(nèi)核根目錄以下件:

  Documentation\devicetree\bindings\i2c\i2c-s3c2410.txt,。

  不同的公司設(shè)計(jì)的i2c控制器設(shè)備樹(shù)節(jié)點(diǎn)信息填寫(xiě)格式不盡相同,,需要根據(jù)具體產(chǎn)品填寫(xiě)。

  編寫(xiě)驅(qū)動(dòng)代碼

  分配,、設(shè)置,、注冊(cè)i2c_driver結(jié)構(gòu)體

  

33.png

  i2c總線驅(qū)動(dòng)模型屬于設(shè)備模型中的一類(lèi),同樣struct i2c_driver結(jié)構(gòu)體繼承于struct driver,,匹配方法和設(shè)備模型中講的一樣,,這里要去匹配設(shè)備樹(shù),所以必須實(shí)現(xiàn)i2c_driver結(jié)構(gòu)體中的driver成員中的of_match_table成員:

  

34.png

  如果和設(shè)備樹(shù)匹配成功,,那么就會(huì)調(diào)用probe函數(shù)

  

35.png

  實(shí)現(xiàn)文件操作集合

  

36.png

  如何填充i2c_msg,?

  根據(jù)mpu6050的datasheet可知,向mpu6050寫(xiě)入1個(gè)data和讀取1個(gè)值的時(shí)序分別如下圖所示,。

  

37.png

  基于Linux的i2c架構(gòu)編寫(xiě)驅(qū)動(dòng)程序,,我們需要用struct i2c_msg結(jié)構(gòu)體來(lái)表示上述所有信息。

  

38.png

  編寫(xiě)i2c_msg信息原則如下:

  有幾個(gè)S信號(hào),,msg數(shù)組就要有幾個(gè)元素,;

  addr為從設(shè)備地址,通過(guò)i2c總線調(diào)用注冊(cè)的probe函數(shù)的參數(shù)i2c_client傳遞下來(lái),;

  len的長(zhǎng)度不包括S,、AD、ACK,、P,;

  buf為要發(fā)送或者要讀取的DATA的內(nèi)存地址,。

  綜上所述:

  Single-Byte Write Sequence時(shí)序只需要1個(gè)i2c_msg,len值為2,,buf內(nèi)容為是RA、DATA;

  Single-Byte Read Sequence時(shí)序需要2個(gè)i2c_msg,len值分別都為1,,第1個(gè)msg的buf是RA,,第2個(gè)msg的buf緩沖區(qū)用于存取從設(shè)備發(fā)送的DATA。

  

39.png

  I2C內(nèi)核架構(gòu)分析

  本章以linux3.14.0為參考,, 討論Linux中的i2c控制器驅(qū)動(dòng)是如何實(shí)現(xiàn)的,。

  驅(qū)動(dòng)入口

  三星的i2c控制器驅(qū)動(dòng)是基于platform總線實(shí)現(xiàn)的,struct platform_driver定義如下:

  

40.png

  當(dāng)設(shè)備樹(shù)節(jié)點(diǎn)信息的compatible信息和注冊(cè)的platform_driver.driver. of_match_table字符串會(huì)通過(guò)platform總線的macth方法進(jìn)行配對(duì),,匹配成功后會(huì)調(diào)用probe函數(shù)s3c24xx_i2c_probe(),。

  驅(qū)動(dòng)核心結(jié)構(gòu)

  要理解i2c的內(nèi)核架構(gòu)首先必須了解一下這幾個(gè)機(jī)構(gòu)體:

  s3c24xx_i2c

  該結(jié)構(gòu)體是三星i2c控制器專(zhuān)用結(jié)構(gòu)體,描述了控制器的所有資源,,包括用于等待中斷喚醒的等待隊(duì)列,、傳輸i2c_msg的臨時(shí)指針、記錄與硬件通信的狀態(tài),、中斷號(hào),、控制器基地址、時(shí)鐘,、i2c_adapter,、設(shè)備樹(shù)信息pdata等。i2c控制器初始化的時(shí)候會(huì)為該控制器創(chuàng)建該結(jié)構(gòu)體變量,,并初始化之,。

  i2c_adapter

  對(duì)象實(shí)現(xiàn)了一組通過(guò)一個(gè)i2c控制器發(fā)送消息的所有信息, 包括時(shí)序,, 地址等等,, 即封裝了i2c控制器的"控制信息"。它被i2c主機(jī)驅(qū)動(dòng)創(chuàng)建,, 通過(guò)clien域和i2c_client和i2c_driver相連,, 這樣設(shè)備端驅(qū)動(dòng)就可以通過(guò)其中的方法以及i2c物理控制器來(lái)和一個(gè)i2c總線的物理設(shè)備進(jìn)行交互。

  i2c_algorithm

  描述一個(gè)i2c主機(jī)的發(fā)送時(shí)序的信息,,該類(lèi)的對(duì)象algo是i2c_adapter的一個(gè)域,,其中注冊(cè)的函數(shù)master_xfer()最終被設(shè)備驅(qū)動(dòng)端的i2c_transfer()回調(diào)。

  i2c_msg

  描述一個(gè)在設(shè)備端和主機(jī)端之間進(jìn)行流動(dòng)的數(shù)據(jù),, 在設(shè)備驅(qū)動(dòng)中打包并通過(guò)i2c_transfer()發(fā)送,。相當(dāng)于skbuf之于網(wǎng)絡(luò)設(shè)備,urb之于USB設(shè)備,。

  這幾個(gè)結(jié)構(gòu)體之間關(guān)系:

  

41.png

  i2c_client

  描述一個(gè)掛接在硬件i2c總線上的設(shè)備的設(shè)備信息,,即i2c設(shè)備的設(shè)備對(duì)象,與i2c_driver對(duì)象匹配成功后通過(guò)detected和i2c_driver以及i2c_adapter相連,,在控制器驅(qū)動(dòng)與控制器設(shè)備匹配成功后被控制器驅(qū)動(dòng)通過(guò)i2c_new_device()創(chuàng)建,。從設(shè)備所掛載的i2c控制器會(huì)在初始化的時(shí)候保存到成員adapter,。

  i2c_driver

  描述一個(gè)掛接在硬件i2c總線上的設(shè)備的驅(qū)動(dòng)方法,即i2c設(shè)備的驅(qū)動(dòng)對(duì)象,,通過(guò)i2c_bus_type和設(shè)備信息i2c_client匹配,,匹配成功后通過(guò)clients和i2c_client對(duì)象以及i2c_adapter對(duì)象相連。

  

42.png

  如上圖所示:Linux內(nèi)核維護(hù)了i2c bus總線,,所有的i2c從設(shè)備信息都會(huì)轉(zhuǎn)換成i2c_client,并注冊(cè)到i2c總線,,沒(méi)有設(shè)備的情況下一般填寫(xiě)在一下文件中:

  linux-3.14-fs4412\arch\arm\mach-s5pc100\ Mach-smdkc100.c

  

43.png

  內(nèi)核啟動(dòng)會(huì)將i2c_board_info結(jié)構(gòu)體轉(zhuǎn)換成i2c_client。

  有設(shè)備樹(shù)的情況下,,內(nèi)核啟動(dòng)會(huì)自動(dòng)將設(shè)備樹(shù)節(jié)點(diǎn)轉(zhuǎn)換成i2c_client,。

  i2c_adapter

  我首先說(shuō)i2c_adapter, 并不是編寫(xiě)一個(gè)i2c設(shè)備驅(qū)動(dòng)需要它, 通常我們?cè)谂渲脙?nèi)核的時(shí)候已經(jīng)將i2c控制器的設(shè)備信息和驅(qū)動(dòng)已經(jīng)編譯進(jìn)內(nèi)核了,, 就是這個(gè)adapter對(duì)象已經(jīng)創(chuàng)建好了,, 但是了解其中的成員對(duì)于理解i2c驅(qū)動(dòng)框架非常重要, 所有的設(shè)備驅(qū)動(dòng)都要經(jīng)過(guò)這個(gè)對(duì)象的處理才能和物理設(shè)備通信

  //include/linux/i2c.h

  

44.png

  428-->這個(gè)i2c控制器需要的控制算法,, 其中最重要的成員是master_xfer()接口,, 這個(gè)接口是硬件相關(guān)的, 里面的操作都是基于具體的SoC i2c寄存器的,, 它將完成將數(shù)據(jù)發(fā)送到物理i2c控制器的"最后一公里"

  436-->表示這個(gè)一個(gè)device, 會(huì)掛接到內(nèi)核中的鏈表中來(lái)管理,, 其中的

  443-->這個(gè)節(jié)點(diǎn)將一個(gè)i2c_adapter對(duì)象和它所屬的i2c_client對(duì)象以及相應(yīng)的i2c_driver對(duì)象連接到一起

  下面是2個(gè)i2c-core.c提供的i2c_adapter直接相關(guān)的操作API, 通常也不需要設(shè)備驅(qū)動(dòng)開(kāi)發(fā)中使用。

  Adapter初始化

  i2c控制器設(shè)備樹(shù)節(jié)點(diǎn)信息通過(guò)platform總線傳遞下來(lái),,即參數(shù)pdev,。probe函數(shù)主要功能是初始化adapter,申請(qǐng)i2c控制器需要的各種資源,,同時(shí)通過(guò)設(shè)備樹(shù)節(jié)點(diǎn)初始化該控制器下的所有從設(shè)備,,創(chuàng)建i2c_client結(jié)構(gòu)體。

  static int   s3c24xx_i2c_probe(struct platform_device *pdev)

  {

  struct s3c24xx_i2c *i2c;//最重要的結(jié)構(gòu)體

  //保存設(shè)備樹(shù)信息

  struct s3c2410_platform_i2c *pdata =   NULL;

  struct resource *res;

  int ret;

  if (,!pdev->dev.of_node) {

  pdata =   dev_get_platdata(&pdev->dev),;

  if (!pdata) {

  dev_err(&pdev->dev,   "no platform data\n"),;

  return -EINVAL;

  }

  }

  /*為結(jié)構(gòu)體變量i2c分配內(nèi)存*/

  i2c = devm_kzalloc(&pdev->dev,   sizeof(struct s3c24xx_i2c),, GFP_KERNEL);

  if (,!i2c) {

  dev_err(&pdev->dev,   "no memory for state\n"),;

  return -ENOMEM;

  }

  i2c->pdata =   devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL),;

  if (,!i2c->pdata) {

  dev_err(&pdev->dev,   "no memory for platform data\n");

  return -ENOMEM;

  }

  /*i2c控制器的一些特殊行為

  #define QUIRK_S3C2440              (1 《 0)

  #define QUIRK_HDMIPHY            (1 《 1)

  #define QUIRK_NO_GPIO             (1 《 2)

  #define QUIRK_POLL            (1 《 3)

  其中bite:3如果采用輪訓(xùn)方式與底層硬件通信值為1,,中斷方式值為0*/

  i2c->quirks =   s3c24xx_get_device_quirks(pdev),;

  if (pdata)

  memcpy(i2c->pdata, pdata,   sizeof(*pdata)),;

  else

  s3c24xx_i2c_parse_dt(pdev->dev.of_node,   i2c);

  strlcpy(i2c->adap.name,   "s3c2410-i2c", sizeof(i2c->adap.name)),;

  i2c->adap.owner   = THIS_MODULE;

  /*為i2c_msg傳輸方法賦值,*/

  i2c->adap.algo    = &s3c24xx_i2c_algorithm;

  i2c->adap.retries = 2;

  i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;

  i2c->tx_setup     = 50;

  //初始化等待隊(duì)列,,該等待隊(duì)列用于喚醒讀寫(xiě)數(shù)據(jù)的進(jìn)程

  init_waitqueue_head(&i2c->wait),;

  /* find the clock and enable it */

  i2c->dev = &pdev->dev;

  //獲取時(shí)鐘

  i2c->clk =   devm_clk_get(&pdev->dev, "i2c");

  if (IS_ERR(i2c->clk)) {

  dev_err(&pdev->dev,   "cannot get clock\n"),;

  return -ENOENT;

  }

  dev_dbg(&pdev->dev, "clock   source %p\n", i2c->clk),;

  /* map the registers */

  //通過(guò)pdev得到i2c控制器的寄存器地址資源

  res = platform_get_resource(pdev,   IORESOURCE_MEM, 0);

  //映射i2c控制器的物理基地址為虛擬基地址

  i2c->regs =   devm_ioremap_resource(&pdev->dev, res),;

  if (IS_ERR(i2c->regs))

  return PTR_ERR(i2c->regs),;

  dev_dbg(&pdev->dev,   "registers %p (%p)\n",

  i2c->regs, res);

  /* setup info block for the i2c core */

  /*將結(jié)構(gòu)體變量i2c保存到i2c_adapter的私有變量指針algo_data,,

  編寫(xiě)i2c設(shè)備驅(qū)動(dòng)可以通過(guò)adapter指針找到結(jié)構(gòu)體i2c*/

  i2c->adap.algo_data = i2c;

  i2c->adap.dev.parent =   &pdev->dev;

  i2c->pctrl =   devm_pinctrl_get_select_default(i2c->dev),;

  /* inititalise the i2c gpio lines */

  //得到i2c復(fù)用的gpio引腳并初始化

  if (i2c->pdata->cfg_gpio) {

  i2c->pdata->cfg_gpio(to_platform_device(i2c->dev));

  } else if (IS_ERR(i2c->pctrl)   && s3c24xx_i2c_parse_dt_gpio(i2c)) {

  return -EINVAL;

  }

  /* initialise the i2c controller */

  clk_prepare_enable(i2c->clk),;

  /*將從設(shè)備地址寫(xiě)入寄存器S3C2410_IICADD,,同時(shí)初始化時(shí)鐘頻率*/

  ret = s3c24xx_i2c_init(i2c);

  clk_disable_unprepare(i2c->clk),;

  if (ret != 0) {

  dev_err(&pdev->dev,   "I2C controller init failed\n"),;

  return ret;

  }

  /* find the IRQ for this unit (note,   this relies on the init call to

  * ensure no current IRQs pending

  */

  if (!(i2c->quirks & QUIRK_POLL))   {

  /*從plat_device中獲得中斷號(hào)*/

  i2c->irq = ret =   platform_get_irq(pdev, 0),;

  if (ret <= 0) {

  dev_err(&pdev->dev,   "cannot find IRQ\n"),;

  return ret;

  }

  /*注冊(cè)中斷處理函數(shù)s3c24xx_i2c_irq()*/

  ret =   devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq, 0,

  dev_name(&pdev->dev),   i2c),;

  if (ret != 0) {

  dev_err(&pdev->dev,   "cannot claim IRQ %d\n", i2c->irq),;

  return ret;

  }

  }

  ret =   s3c24xx_i2c_register_cpufreq(i2c);

  if (ret < 0) {

  dev_err(&pdev->dev,   "failed to register cpufreq notifier\n"),;

  return   ret;

  }

  /* Note, previous versions of the   driver used i2c_add_adapter()

  * to add the bus at any number. We now pass   the bus number via

  * the platform data, so if unset it will now   default to always

  * being bus 0.

  */

  /*保存i2c控制器的通道號(hào),,本例是bus 5*/

  i2c->adap.nr =   i2c->pdata->bus_num;

  i2c->adap.dev.of_node =   pdev->dev.of_node;

  //注冊(cè)adapter

  ret =   i2c_add_numbered_adapter(&i2c->adap);

  if (ret < 0) {

  dev_err(&pdev->dev,   "failed to add bus to i2c core\n"),;

  s3c24xx_i2c_deregister_cpufreq(i2c),;

  return ret;

  }

  /*保存私有變量i2c到pdev->dev->p->driver_data*/

  platform_set_drvdata(pdev, i2c);

  pm_runtime_enable(&pdev->dev),;

  pm_runtime_enable(&i2c->adap.dev),;

  dev_info(&pdev->dev, "%s:   S3C I2C adapter\n", dev_name(&i2c->adap.dev));

  return 0;

  }

  老版本的注冊(cè)函數(shù)為i2c_add_adapter()新的版本對(duì)該函數(shù)做了封裝,,將i2c控制的通道號(hào)做了注冊(cè),,默認(rèn)情況下nr值為0.

  i2c_add_numbered_adapter->__i2c_add_numbered_adapter-> i2c_register_adapter

  int   i2c_add_numbered_adapter(struct i2c_adapter *adap)

  {

  if (adap->nr == -1) /* -1 means   dynamically assign bus id */

  return i2c_add_adapter(adap),;

  return __i2c_add_numbered_adapter(adap);

  }

  static int   i2c_register_adapter(struct i2c_adapter *adap)

  {

  int res = 0;

  /* Can't register until after driver   model init */

  if (unlikely(WARN_ON(,!i2c_bus_type.p)))   {

  res = -EAGAIN;

  goto out_list;

  }

  /* Sanity checks */

  if (unlikely(adap->name[0] == '\0'))   {

  pr_err("i2c-core: Attempt   to register an adapter with "

  "no name!\n"),;

  return -EINVAL;

  }

  if (unlikely(!adap->algo)) {

  pr_err("i2c-core: Attempt   to register adapter '%s' with "

  "no algo!\n",   adap->name),;

  return -EINVAL;

  }

  rt_mutex_init(&adap->bus_lock),;

  mutex_init(&adap->userspace_clients_lock);

  INIT_LIST_HEAD(&adap->userspace_clients),;

  /* Set default timeout to 1 second if   not already set */

  if (adap->timeout == 0)

  adap->timeout = HZ;

  //設(shè)置adapter名字,,本例注冊(cè)后會(huì)生成以下節(jié)點(diǎn)/dev/i2c-5

  dev_set_name(&adap->dev,   "i2c-%d", adap->nr);

  adap->dev.bus = &i2c_bus_type;

  adap->dev.type = &i2c_adapter_type;

  res =   device_register(&adap->dev),;

  if (res)

  goto out_list;

  dev_dbg(&adap->dev,   "adapter [%s] registered\n", adap->name),;

  #ifdef   CONFIG_I2C_COMPAT

  res =   class_compat_create_link(i2c_adapter_compat_class, &adap->dev,

  adap->dev.parent);

  if (res)

  dev_warn(&adap->dev,

  "Failed to create compatibility class   link\n"),;

  #endif

  /* bus recovery specific initialization   */

  /*初始化sda,、scl,通常這兩個(gè)引腳會(huì)復(fù)用gpio引腳*/

  if (adap->bus_recovery_info) {

  struct i2c_bus_recovery_info   *bri = adap->bus_recovery_info;

  if (,!bri->recover_bus) {

  dev_err(&adap->dev,   "No recover_bus() found, not using recovery\n"),;

  adap->bus_recovery_info   = NULL;

  goto exit_recovery;

  }

  /* Generic GPIO recovery */

  if (bri->recover_bus ==   i2c_generic_gpio_recovery) {

  if   (!gpio_is_valid(bri->scl_gpio)) {

  dev_err(&adap->dev,   "Invalid SCL gpio, not using recovery\n"),;

  adap->bus_recovery_info   = NULL;

  goto   exit_recovery;

  }

  if   (gpio_is_valid(bri->sda_gpio))

  bri->get_sda =   get_sda_gpio_value;

  else

  bri->get_sda =   NULL;

  /*sda,、scl資源賦值*/

  bri->get_scl =   get_scl_gpio_value;

  bri->set_scl =   set_scl_gpio_value;

  } else if (!bri->set_scl ||   !bri->get_scl) {

  /* Generic SCL recovery   */

  dev_err(&adap->dev,   "No {get|set}_gpio() found, not using recovery\n"),;

  adap->bus_recovery_info   = NULL;

  }

  }

  exit_recovery:

  /* create pre-declared device nodes */

  /*通過(guò)設(shè)備樹(shù)節(jié)點(diǎn)注冊(cè)所有該控制器下的所有從設(shè)備*/

  of_i2c_register_devices(adap),;

  acpi_i2c_register_devices(adap);

  /*與動(dòng)態(tài)分配的總線號(hào)相關(guān),,動(dòng)態(tài)分配的總線號(hào)應(yīng)該是從已經(jīng)現(xiàn)有最大總線號(hào)基礎(chǔ)上+1的,,

  這樣能夠保證動(dòng)態(tài)分配出的總線號(hào)與板級(jí)總線號(hào)不會(huì)產(chǎn)生沖突

  在沒(méi)有設(shè)備樹(shù)情況下,會(huì)基于隊(duì)列__i2c_board_list, 創(chuàng)建i2c_client

  其中節(jié)點(diǎn)struct i2c_board_info手動(dòng)填寫(xiě)*/

  if (adap->nr <   __i2c_first_dynamic_bus_num)

  i2c_scan_static_board_info(adap),;

  /* Notify drivers */

  mutex_lock(&core_lock),;

  bus_for_each_drv(&i2c_bus_type,   NULL, adap, __process_new_adapter);

  mutex_unlock(&core_lock),;

  return 0;

  out_list:

  mutex_lock(&core_lock),;

  idr_remove(&i2c_adapter_idr,   adap->nr);

  mutex_unlock(&core_lock),;

  return res;

  }

  該函數(shù)用于將從設(shè)備節(jié)點(diǎn)轉(zhuǎn)換成i2c_client,,并注冊(cè)到i2c總線上。

  static void   of_i2c_register_devices(struct i2c_adapter *adap)

  {

  void *result;

  struct device_node *node;

  /* Only register child devices if the   adapter has a node pointer set */

  if (,!adap->dev.of_node)

  return;

  dev_dbg(&adap->dev,   "of_i2c: walking child nodes\n"),;

  for_each_available_child_of_node(adap->dev.of_node,   node) {

  struct i2c_board_info info = {};

  struct dev_archdata dev_ad = {};

  const __be32 *addr;

  int len;

  dev_dbg(&adap->dev,   "of_i2c: register %s\n", node->full_name),;

  if (of_modalias_node(node,   info.type, sizeof(info.type)) < 0) {

  dev_err(&adap->dev,   "of_i2c: modalias failure on %s\n",

  node->full_name);

  continue;

  }

  /*獲取從設(shè)備的地址*/

  addr = of_get_property(node,   "reg", &len),;

  if (,!addr || (len <   sizeof(int))) {

  dev_err(&adap->dev,   "of_i2c: invalid reg on %s\n",

  node->full_name);

  continue;

  }

  /*存儲(chǔ)從設(shè)備地址*/

  info.addr = be32_to_cpup(addr),;

  if (info.addr > (1 《   10) - 1) {

  dev_err(&adap->dev,   "of_i2c: invalid addr=%x on %s\n",

  info.addr,   node->full_name),;

  continue;

  }

  /*獲取中斷號(hào)*/

  info.irq =   irq_of_parse_and_map(node, 0);

  info.of_node =   of_node_get(node),;

  info.archdata = &dev_ad;

  /*獲取設(shè)備樹(shù)節(jié)點(diǎn)wakeup-source信息*/

  if (of_get_property(node,   "wakeup-source", NULL))

  info.flags |=   I2C_CLIENT_WAKE;

  request_module("%s%s",   I2C_MODULE_PREFIX, info.type),;

  /*將i2c_board_info轉(zhuǎn)換成i2c_client并注冊(cè)到i2c總線*/

  result = i2c_new_device(adap,   &info),;

  if (result == NULL) {

  dev_err(&adap->dev,   "of_i2c: Failure registering %s\n",

  node->full_name),;

  of_node_put(node);

  irq_dispose_mapping(info.irq),;

  continue;

  }

  }

  }

  將i2c_board_info轉(zhuǎn)換成i2c_client并注冊(cè)到Linux核心,。

  {

  struct i2c_client      *client;

  int                 status;

  /*給i2c_client分配內(nèi)存*/

  client = kzalloc(sizeof *client, GFP_KERNEL);

  if (,!client)

  return NULL;

  /*將adapter的地址保存到i2c_client->adapter,,

  在驅(qū)動(dòng)函數(shù)中可以通過(guò)i2c_client找到adapter*/

  client->adapter = adap;

  client->dev.platform_data =   info->platform_data;

  if (info->archdata)

  client->dev.archdata =   *info->archdata;

  /*保存從設(shè)備地址類(lèi)型*/

  client->flags = info->flags;

  /*保存從設(shè)備地址*/

  client->addr = info->addr;

  /*保存從設(shè)備中斷號(hào)*/

  client->irq = info->irq;

  strlcpy(client->name, info->type,   sizeof(client->name));

  /* Check for address validity */

  /*檢測(cè)從設(shè)備地址是否合法,,主要檢查位數(shù)*/

  status =   i2c_check_client_addr_validity(client),;

  if (status) {

  dev_err(&adap->dev,   "Invalid %d-bit I2C address 0x%02hx\n",

  client->flags &   I2C_CLIENT_TEN ? 10 : 7, client->addr);

  goto out_err_silent;

  }

  /* Check for address business */

  /*檢測(cè)從設(shè)備地址是否被占用,,同一個(gè)控制器下同一個(gè)從設(shè)備地址只能注冊(cè)一次*/

  status = i2c_check_addr_busy(adap,   client->addr),;

  if (status)

  goto out_err;

  /*建立從設(shè)備與適配器的父子關(guān)系*/

  client->dev.parent =   &client->adapter->dev;

  client->dev.bus = &i2c_bus_type;

  client->dev.type =   &i2c_client_type;

  client->dev.of_node = info->of_node;

  ACPI_COMPANION_SET(&client->dev,   info->acpi_node.companion);

  i2c_dev_set_name(adap, client),;

  /*注冊(cè)到Linux核心*/

  status =   device_register(&client->dev),;

  if (status)

  goto out_err;

  dev_dbg(&adap->dev, "client   [%s] registered with bus id %s\n",

  client->name,   dev_name(&client->dev));

  return client;

  out_err:

  dev_err(&adap->dev, "Failed   to register i2c client %s at 0x%02x "

  "(%d)\n",   client->name, client->addr, status),;

  out_err_silent:

  kfree(client),;

  return NULL;

  }

  i2c_msg如何傳遞?

  l  i2c_transfer()是i2c核心提供給設(shè)備驅(qū)動(dòng)的發(fā)送方法,, 通過(guò)它發(fā)送的數(shù)據(jù)需要被打包成i2c_msg, 這個(gè)函數(shù)最終會(huì)回調(diào)相應(yīng)i2c_adapter->i2c_algorithm->master_xfer()接口將i2c_msg對(duì)象發(fā)送到i2c物理控制器,,

  i2c_adapte->algo在函數(shù)s3c24xx_i2c_probe()中賦值:

  

45.png

  該變量定義如下:

  

46.png

  i2c_transfer()最終會(huì)調(diào)用函數(shù)s3c24xx_i2c_xfer();

  以下是一次i2c_msg傳輸?shù)闹袛嗄J降拇蟾挪襟E:

  

47.png

  1. i2c_transfer()首先通過(guò)函數(shù)i2c_trylock_adapter()嘗試獲得adapter的控制權(quán),。如果adapter正在忙則返回錯(cuò)誤信息,;

  2. __i2c_transfer()通過(guò)調(diào)用方法adap->algo->master_xfer(adap, msgs, num)傳輸i2c_msg,如果失敗會(huì)嘗試重新傳送,重傳次數(shù)最多adap->retries,;

  3. adap->algo->master_xfer()就是函數(shù)s3c24xx_i2c_xfer(),,該函數(shù)最終調(diào)用 s3c24xx_i2c_doxfer(i2c, msgs, num)傳輸信息,;

  4. s3c24xx_i2c_doxfer()通過(guò)函數(shù)     s3c24xx_i2c_message_start(i2c, msgs)產(chǎn)生S和AD+W的信號(hào),然后通過(guò)函數(shù)wait_event_timeout( )阻塞在等待隊(duì)列i2c->wait上,;

  5. 右上角時(shí)序mpu6050的寫(xiě)和讀的時(shí)序,,從設(shè)備回復(fù)ACK和DATA都會(huì)發(fā)送中斷信號(hào)給CPU。每次中斷都會(huì)調(diào)用s3c24xx_i2c_irq->i2c_s3c_irq_nextbyte,

  6. 最后一次中斷,,所有數(shù)據(jù)發(fā)送或讀取完畢會(huì)調(diào)用s3c24xx_i2c_stop->s3c24xx_i2c_master_complete,,通過(guò)wake_up喚醒阻塞在等待隊(duì)列i2c->wait上的任務(wù)。

  詳細(xì)的代碼流程如下:

  

48.png

  i2c_transfer()首先通過(guò)函數(shù)i2c_trylock_adapter()嘗試獲得adapter的控制權(quán),。如果adapter正在忙則返回錯(cuò)誤信息,;

  __i2c_transfer()通過(guò)調(diào)用方法adap->algo->master_xfer(adap,msgs, num)傳輸i2c_msg,如果失敗會(huì)嘗試重新傳送,重傳次數(shù)最多adap->retries,;

  adap->algo->master_xfer()就是函數(shù)s3c24xx_i2c_xfer(),,該函數(shù)最終調(diào)用 s3c24xx_i2c_doxfer(i2c, msgs, num)傳輸信息;

  s3c24xx_i2c_doxfer()通過(guò)函數(shù)     s3c24xx_i2c_message_start(i2c, msgs)產(chǎn)生S和AD+W的信號(hào),,然后通過(guò)函數(shù)wait_event_timeout()阻塞在等待隊(duì)列i2c->wait上,;

  右上角時(shí)序mpu6050的寫(xiě)和讀的時(shí)序,從設(shè)備回復(fù)ACK和DATA都會(huì)發(fā)送中斷信號(hào)給CPU,。每次中斷都會(huì)調(diào)用s3c24xx_i2c_irq->i2c_s3c_irq_nextbyte,

  最后一次中斷,,所有數(shù)據(jù)發(fā)送或讀取完畢會(huì)調(diào)用s3c24xx_i2c_stop->s3c24xx_i2c_master_complete,通過(guò)wake_up喚醒阻塞在等待隊(duì)列i2c->wait上的任務(wù),。

  詳細(xì)的代碼流程如下:

  

49.png

  對(duì)著可以根據(jù)上圖代碼行號(hào)一步步去跟代碼,,涉及到寄存器設(shè)置可以參考第一章的寄存器使用截圖。



更多信息可以來(lái)這里獲取==>>電子技術(shù)應(yīng)用-AET<<

圖片.jpg


本站內(nèi)容除特別聲明的原創(chuàng)文章之外,,轉(zhuǎn)載內(nèi)容只為傳遞更多信息,,并不代表本網(wǎng)站贊同其觀點(diǎn)。轉(zhuǎn)載的所有的文章,、圖片,、音/視頻文件等資料的版權(quán)歸版權(quán)所有權(quán)人所有。本站采用的非本站原創(chuàng)文章及圖片等內(nèi)容無(wú)法一一聯(lián)系確認(rèn)版權(quán)者,。如涉及作品內(nèi)容,、版權(quán)和其它問(wèn)題,請(qǐng)及時(shí)通過(guò)電子郵件或電話通知我們,,以便迅速采取適當(dāng)措施,,避免給雙方造成不必要的經(jīng)濟(jì)損失。聯(lián)系電話:010-82306118,;郵箱:[email protected],。