《電子技術應用》
您所在的位置:首頁 > 模擬設計 > 業界動態 > I2C干貨-基于Cortex-A9(重新整理)

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

2022-10-04
來源:FPGA之家
關鍵詞: I2C Cortex-A9 三星

  本文基于三星Cortex-A9架構,Exynos4412講解I2C原理、以及基于I2C的mpu6050陀螺儀的數據讀取實例(包括在裸機模式下數據的讀取以及基于Linux驅動的讀取)。還會分析Linux內核I2C架構,篇幅過長,絕對干貨。

  裸機篇

  本篇首先詳細講解I2C時序,然后講解如何基于三星I2C控制實現裸機讀取從設備信息方法。

  前言

  I2C(Inter-Integrated Circuit)總線(也稱 IIC 或 I2C) 是有PHILIPS公司開發的兩線式串行總線,用于連接微控制器及外圍設備,是微電子通信控制領域廣泛采用的一種總線標準。它是同步通信的一種特殊形式,具有接口線少、控制方式簡單、器件封裝形式小、通信速率較高等優點。

  

1.png

  Exynos4412 i2c控制器綜述

  Exynos4412精簡指令集微處理器支持4個IIC總線控制器。為了能使連接在總線上的主和從設備之間傳輸數據,專用的數據線SDA和時鐘信號線SCL被使用,他們都是雙向的。

  如果工作在多主機的IIC總線模式,多個4412處理器將從從機那接收數據或發送數據給從機。在IIC總線上的主機端4412會啟動或終止一個數據傳輸。4412的IIC總線控制器會用一個標準的IIC總線仲裁機制去實現多主機和多從機傳輸數據。

  通過控制如下寄存器以實現IIC總線上的多主機操作:

  控制寄存器:                   I2CCON

  狀態寄存器:                   I2CSTAT

  Tx/Rx數據偏移寄存器:  I2CDS

  地址寄存器:                   I2CADD

  如果I2C總線空閑,那么SCL和SDA信號線將都為高電平。在SCL為高電平期間,如果SDA有由高到低電平的跳變,那么將啟動一個起始信號,如果SDA有由低到高電平的跳變,將啟動一個結束信號。

  主機端的設備總是提供起始和停止信號的一端。在起始信號被發出后,一個數據字節的前7位被當作地址通過SDA線被傳輸。這個地制值決定了總線上的主設備將要選擇那個從設備作為傳輸對象,bit8決定傳輸數據的方向(是讀還是寫)。

  I2C總線上的數據(即在SDA上傳輸的數據)都是以8位字節傳輸的,在總線上傳輸操作的過程中,對發送或接收的數據字節數是沒有限制的。I2C總線上的主/從設備發送數據總是以一個數據的最高位開始傳輸(即MSB方式),傳輸完一個字節后,應答信號緊接其后。

  Exynos4412 I2C總線接口特性

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

  7位地址模式。

  串行,8位單向或雙向的數據傳輸。

  在標準模式中,每秒最多可以傳輸100k位,即12.5kB的數據量。

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

  支持主機端發送、接收,從機端發送、接收操作。

  支持中斷和查詢方式。

  框圖

  

2.png

  從上圖可以看出,4412提供4個寄存器來完成所有的IIC操作。SDA線上的數據從IICDS寄存器經過移位寄存器發出,或通過移位寄存器傳入IICDS寄器;IICADD寄存器中保存4412當做從機時的地址;IICCON、IICSTAT兩個寄存器用來控制或標識各種狀態,比如選擇工作工作模式,發出S信號、P信號,決定是否發出ACK信號,檢測是否接收到ACK信號。

  I2C總線接口操作

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

  主機發送模式

  主機接收模式

  從機發送模式

  從機接收模式

  下面將描述這些操作模式之間的功能關系:

  0、數據有效性

  

3.png

  SDA線上的數據必須在時鐘的高電平周期保持穩定。數據線的高或低電平狀態IIC位傳輸數據的有效性在SCL線的時鐘信號是低電平才能改變。

  1.  開始和停止條件

  當4412的I2C接口空閑時,它往往工作在從機模式。或者說,4412的的i2c接口在SDA線上察覺到一個起始信號之前它應該工作在從機模式。當控制器改變4412的i2c接口的工作模式為主機模式后,SDA線上發起數據傳輸并且控制器會產生SCL時鐘信號。

  開始條件通過SDA線進行串行的字節傳輸,一個停止信號終止數據傳輸,停止信號是指SCL在高電平器件SDA線有從低到高電平的跳變,主機端產生起始和停止條件。當主、從設備產生一個起始信號后,I2C總線將進入忙狀態。這里需要說明的是上述主從設備都有可能作為主機端。

  當一個主機發送了一個起始信號后,它也應該發送一個從機地址以通知總線上的從設備。這個地址字節的低7位表示從設備地址,最高位表示傳輸數據的方向,即主機將要進行讀還是寫。當最高位是0時,它將發起一個寫操作(發送操作);當最高位是1時,它將發起一個讀數據的請求(接收操作)。

  主機端發起一個結束信號以完成傳輸操作,如果主機端想在總線上繼續進行數據的傳輸,它將發出另外一個起始信號和從設備地址。用這樣的方式,它們可以用各種各樣的格式進行讀寫操作。

  下圖為起始和停止信號:

  

4.png

  2.  數據傳輸格式

  放到SDA線上的所有字節數據的長度應該為8位,在每次傳輸數據時,對傳輸數據量沒有限制。在起始信號后的第一個數據字節應該包含地址字段,當4412的I2C接口被設置為主模式時,地址字節應該由控制器端發出。在每個字節后,應該有一個應答位。

  如果從機要完成一些其他功能后(例如一個內部中斷服務程序)才能繼續接收或發送下一個字節,從機可以拉低SCL迫使主機進入等待狀態。當從機準備好接收下一個數據并釋放SCL后,數據傳輸繼續。如果主機在傳輸數據期間也需要完成一些其他功能(例如一個內部中斷服務程序)也可以拉低SCL以占住總線。

  下面的圖中將說明數據傳輸格式:

  

5.png

  上圖中說明,在傳輸完每個字節數據后,都會有一個應答信號,這個應答信號在第9個時鐘周期。具體過程如下(注意下面描述的讀寫過程都是針對 4412處理器而言,當有具體的I2C設備與4412相連時,數據表示什么需要看具體的I2C設備,4412是不知道數據的含義的):

  寫過程:主機發送一個起始信號S→發送從機7位地址和1位方向,方向位表示寫→主機釋放SDA線方便從機給回應→有從機匹配到地址,拉低SDA線作為ACK→主機重新獲得SDA傳輸8位數據→主機釋放SDA線方便從機給回應→從機收到數據拉低SDA線作為ACK告訴主機數據接收成功→主機發出停止信號。

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

  注意:在具體的I2C通信時,要看I2C設備才能確定讀寫時序,比如下面即將描述的第七大點中的示例,讀寫EEPROM中就會說道具體的數據含義,讀寫過程。

  3. 應答信號的傳輸

  為了完成一個字節數據的傳輸,接收方將發送一個應答位給發送方。應答信號出現在SCL線上的時鐘周期中的第九個時鐘周期,為了發送或接收1個字節的數據,主機端會產生8個時鐘周期,為了傳輸一個ACK位,主機端需要產生一個時鐘脈沖。

  ACK時鐘脈沖到來之際,發送方會在SDA線上設置高電平以釋放SDA線。在ACK時鐘脈沖之間,接收方會驅動和保持SDA線為低電平,這發生在第9個時鐘脈沖為高電平期間。應答信號為低電平時,規定為有效應答位(ACK簡稱應答位),表示接收器已經成功地接收了該字節;應答信號為高電平時,規定為非應答位(NACK),一般表示接收器接收該字節沒有成功。對于反饋有效應答位ACK的要求是,接收器在第9個時鐘脈沖之前的低電平期間將SDA線拉低,并且確保在該時鐘的高電平期間為穩定的低電平。如果接收器是主控器,則在它收到最后一個字節后,發送一個NACK信號(即不發出ACK信號),以通知被控發送器結束數據發送,并釋放SDA線,以便主控接收器發送一個停止信號P。

  

6.png

  4. 讀寫操作

  當I2C控制器在發送模式下發送數據后,I2C總線接口將等待直到移位寄存器(I2CDS)接收到一個數據。在往此寄存器寫入一個新數據前,SCL線應該保持為低電平,寫完數據后,I2C控制器將釋放SCL線。當前正在傳輸的數據傳輸完成后,4412會捕捉到一個中斷,然后cpu將開始往I2CDS寄存器中寫入一個新的數據。

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

  5. 總線仲裁機制

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

  6. 終止條件

  當一個從接收者不能識別從地址時,它將保持SDA線為高電平。在這樣的情況下,主機會產生一個停止信號并且取消數據的傳輸。當終止傳輸產生后,主機端接收器會通過取消ACK的產生以告訴從機端發送器結束發送操作。這將在主機端接收器接收到從機端發送器發送的最后一個字節之后發生,為了讓主機端產生一個停止條件,從機端發送者將釋放SDA線。

  7. 配置I2C總線

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

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

  在I2C總線上執行任何的收發Tx/Rx操作前,應該做如下配置:

 ?。?)在I2CADD寄存器中寫入從設備地址

 ?。?)設置I2CCON控制寄存器

  a. 使能中斷

  b. 定義SCL頻率

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

  下圖為主設備發送模式

  

7.png

  下圖為主設備接收模式

  

8.png

  下圖為從設備發送模式

 

9.png

  下圖為從設備接收

  

10.png

  I2C控制器寄存器

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

  

11.png

  1-- I2C總線控制寄存器

  IICCON寄存器用于控制是否發出ACK信號、設置發送器的時鐘、開啟I2C中斷,并標識中斷是否發生

  

12.png

  使用IICCON寄存器時,有如下注意事項

  發送模式的時鐘頻率由位[6]、位[3:0]聯合決定。另外,當          IICCON[6]=0時,IICCON[3:0]不能取0或1。

  位[4]用來標識是否有I2C中斷發生,讀出為0時標識沒有中斷發生,讀出為1時標識有中斷發生。當此位為1時,SCL線被拉低,此時所以I2C傳輸停止;如果要繼續傳輸,需寫入0清除它。

  中斷在以下3種情況下發生:

  1 -- 當發送地址信息或接收到一個從機地址并且吻合時;

  2 -- 當總線仲裁失敗時;

  3 -- 當發送/接收完一個字節的數據(包括響應位)時;

  基于SDA、SCL線上時間特性的考慮,要發送數據時,先將數據寫入IICDS寄存器,然后再清除中斷。

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

  2 -- I2C狀態寄存器

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

  

13.png

  3 -- I2C數據發送/接收移位寄存器

  

14.png

  fs4412的i2c總線上掛載了mpu6050

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

  

15.png

  【注意】上述兩個時序非常重要,下面我們編寫基于linux的驅動編寫i2c_msg還要再依賴他。

  上述簡化時序的術語解釋如下

  

16.png

  【寄存器使用規則】

  下面先提前講一下具體應用中如何啟動和恢復IIC的傳輸

  啟動或恢復4412的I2C傳輸有以下兩種方法。

  1) 當IICCON[4]即中斷狀態位為0時,通過寫IICSTAT寄存器啟動I2C操作。有以下兩種情況。

  1--在主機模式,

  令IICSTAT[5:4]等于0b11,將發出S信號和IICDS寄存器的數據(尋址),

  令IICSTAT[5:4]等于0b01,將發出P信號。

  2--在從機模式,令IICSTAT[4]等于1將等待其他主機發出S信號及地址信息。

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

  1--對于主機模式,可以按照上面1的方法寫IICSTAT寄存器,恢復I2C操作后即可發出S信號和IICDS寄存器的值(尋址),或發出P信號。

  2--對于發送器,可以將下一個要發送的數據寫入IICDS寄存器中,恢復I2C操作后即可發出這個數據。

  3--對于接收器,可以從IICDS寄存器讀出接收到的數據。最后向IICCON[4]寫入0的同時,設置IICCON[7]以決定是否在接收到下一個數據后是否發出ACK信號。

  MPU6050

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

  

17.png

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

  電路圖

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

  

18.png

  1 AD0接地的  值為 0

  

19.png

  所以從設備地址為0x68;

  2 SCL、SDA連接的i2c_SCL5、i2c_SDA5

  

20.png

  由此可得這兩個信號線復用了GPIO的GPB的2、3引腳;

  3 查閱exynos4412 datasheet 6.2.2 Part 1可得

 

21.png

  所以設置GPIO 的 GPB     【15:8】= 0x33 即可。

  MPU6050內部寄存器

  mpu6050內部寄存器的使用,參考datasheet《MPU-6000 and MPU-6050

  Register Map and Descriptions Revision 4.0 》。

  Mpu6050內部有100多個寄存器。比如:

  

22.png

  這個寄存器是用來設置加速度屬性的,當bit[4:3] 設置為0,表示3個軸的加速度量程最大為±2g。

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

  下面是個IIC總線實例:

  用IIC總線實現CPU與MPU-6050的數據查詢

  具體代碼如下:

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

  // MPU6050常用內部地址,以下地址在mpu6050內部

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

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

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

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

  #define ACCEL_CONFIG 0x1C //加速計自檢、測量范圍及高通濾波頻率,典型值: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地址寄存器(默認數值0x68,只讀)

  #define SlaveAddress 0xD0 //IIC寫入時的地址字節數據,+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)

  {

  /*根據mpu6050的datasheet,要讀取數據必須先執行寫操作:寫入一個從設備地址,

  然后執行讀操作,才能讀取到該內部寄存器的內容*/

  I2C5.I2CDS = slave_addr; //將從機地址寫入I2CDS寄存器中

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

  I2C5.I2CSTAT = 0xf0;    //[7:6]設置為0b11,主機發送模式;

  //往[5:4]位寫0b11,即產生啟動信號,發出IICDS寄存器中的地址

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

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

  I2C5.I2CDS = addr;       //寫命令值

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

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

  I2C5.I2CSTAT = 0xD0; // I2CSTAT[5:4]位寫0b01,發出停止信號

  I2C5.I2CDS = slave_addr | 1;  //表示要讀出數據

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

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

  //往[5:4]位寫0b11,即產生啟動信號,發出IICDS寄存器中的地址

  //    I2C5.I2CCON = I2C5.I2CCON & (~(1 《 4));    如果強行關閉,將讀取不到數據

  while(!(I2C5.I2CCON & (1 《 4)));//等待傳輸結束,接收數據

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

  // I2CCON [4]位清0,繼續傳輸,接收數據,

  // 主機接收器接收到最后一字節數據后,不發出應答信號 no ack

  // 從機發送器釋放SDA線,以允許主機發出P信號,停止傳輸;

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

  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(!(I2C5.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某個內部寄存器的內容*/

  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;

  }

  /*

  *  裸機代碼,不同于LINUX 應用層, 一定加循環控制

  */

  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;

  }

  實驗結果如下:

  ********** 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

  讀寫操作代碼解析:

  寫入一個數據流程:

  

23.png

  讀數據流程:

  

24.png

  上圖閱讀注意點:

  從設備地址是在用的時候應該左移一位|讀寫位,比如寫reg=0x68<1|0,即0xD0;

  主設備發出S信號,需要將I2CSTATn 的bite:5設置為1;

  主設備發出p信號,需要將I2CSTATn 的bite:5設置為0;

  主機發送數據需要將寄存器I2CCONn的bit:4置0,to reume the operation;

  主機等待從設備發送的ack或者data,需要輪訓判斷I2CCONn的bit:4是否置1;

  代碼的理解除了結合功能流程圖、時序圖、源代碼還要結合寄存器說明;

  代碼的編寫順序必須嚴格按照時序和模塊流程圖執行;

  時序中的每一個數據信號(包括ack、data、reg)的產生或者發送對應的代碼都用箭頭以及相同的顏色框處;

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

  I2C Linux驅動篇

  本篇講解mpu6050基于Linux的驅動的實現。

  Linux I2C架構

  Linux內核已經為我們編寫好了I2C的架構,從設備信息可以在內核文件中直接寫死,也可以通過設備樹來提供,我們只需要實現i2c_driver,然后注冊到i2c架構中即可。

  i2c的內核架構源碼位于:

  \drivers\i2c

  I2C核心(i2c_core)

  I2C核心維護了i2c_bus結構體,提供了I2C總線驅動和設備驅動的注冊、注銷方法,維護了I2C總線的驅動、設備鏈表,實現了設備、驅動的匹配探測。此部分代碼由Linux內核提供。

  I2C總線驅動

  I2C總線驅動維護了I2C適配器數據結構(i2c_adapter)和適配器的通信方法數據結構(i2c_algorithm)。所以I2C總線驅動可控制I2C適配器產生start、stop、ACK等。此部分代碼由具體的芯片廠商提供,比如Samsung、高通。

  I2C設備驅動

  I2C設備驅動主要維護兩個結構體:i2c_driver和i2c_client,實現和用戶交互的文件操作集合fops、cdev等。此部分代碼就是驅動開發者需要完成的。

  Linux內核中描述I2C的四個核心結構體

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

  每一個i2c從設備都需要用一個i2c_client結構體來描述,i2c_client對應真實的i2c物理設備device。

  

25.png

  但是i2c_client不是我們自己寫程序去創建的,而是通過以下常用的方式自動創建的:

  方法一: 分配、設置、注冊i2c_board_info

  方法二: 獲取adapter調用i2c_new_device

  方法三: 通過設備樹(devicetree)創建

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

  2)i2c_adapter

  I2C總線適配器,即soc中的I2C總線控制器,硬件上每一對I2C總線都對應一個適配器來控制它。在Linux內核代碼中,每一個adapter提供了一個描述它的結構(struct i2c_adapter),再通過i2c core層將i2c設備與i2c adapter關聯起來。主要用來完成i2c總線控制器相關的數據通信,此結構體在芯片廠商提供的代碼中維護。

  

26.png

  3)i2c_algorithm

  I2C總線數據通信算法,通過管理I2C總線控制器,實現對I2C總線上數據的發送和接收等操作。亦可以理解為I2C總線控制器(適配器adapter)對應的驅動程序,每一個適配器對應一個驅動程序,用來描述適配器和設備之間的通信方法,由芯片廠商去實現的。

  

27.png

  4)i2c_driver

  用于管理I2C的驅動程序和i2c設備(client)的匹配探測,實現與應用層交互的文件操作集合fops、cdev等。

  

28.png

  設備樹

  1. 硬件電路圖如下:

  

29.png

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

  2. 查找exnos4412的datasheet 29.6.1節,對應的基地址為0x138B0000。

  

31.png

  3. 由上圖可知中斷引腳復用的是GPX3_3。

  4. 在上一篇中,我們已經得到mpu6050從設備地址為0x68。

  linux內核中三星已經為I2C控制器和設備節點的編寫提供了說明手冊:

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

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

  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控制器驅動仍然沿用了s3c2410的驅動。

  綜上,最終I2C設備樹節點編寫如下:

  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>;           從設備地址

  interrupt-parent = <&gpx3>;   中斷父節點

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

  };

  };

  其中 外面節點 i2c@138B0000{}是i2c控制器設備樹信息,子節點

  mpu6050-3-asix@68{}是從設備mpu6050的設備樹節點信息。

  【注意】關于設備樹的編譯燒錄,本篇不做詳細說明,后續會開一篇詳細講述設備樹的使用。

  結構體之間關系如下:

  

32.png

  1. 設備樹節點分為控制器和從設備兩部分,控制器節點信息會通過platform總線與控制器驅動匹配,控制器驅動已經由內核提供,結構體如下:

  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. 從設備節點信息最終會通過i2c_bus與i2c_driver匹配,i2c_driver需要由開發者自己注冊,并實現字符設備接口和創建設備節點/dev/mpu6050;

  3. 用戶通過字符設備節點/dev/mpu6050調用內核的注冊的接口函數mpu6050_read_byte、mpu6050_write_byte;

  4. 內核的i2c core模塊提供了i2c協議相關的核心函數,在實現讀寫操作的時候,需要通過一個重要的函數i2c_transfer(),這個函數是i2c核心提供給設備驅動的,通過它發送的數據需要被打包成i2c_msg結構,這個函數最終會回調相應i2c_adapter->i2c_algorithm->master_xfer()接口將i2c_msg對象發送到i2c物理控制器。

  【注】實例所用soc是exynos4412,為三星公司所出品,所以i2c控制器設備樹節點信息可以參考linux內核根目錄以下件:

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

  不同的公司設計的i2c控制器設備樹節點信息填寫格式不盡相同,需要根據具體產品填寫。

  編寫驅動代碼

  分配、設置、注冊i2c_driver結構體

  

33.png

  i2c總線驅動模型屬于設備模型中的一類,同樣struct i2c_driver結構體繼承于struct driver,匹配方法和設備模型中講的一樣,這里要去匹配設備樹,所以必須實現i2c_driver結構體中的driver成員中的of_match_table成員:

  

34.png

  如果和設備樹匹配成功,那么就會調用probe函數

  

35.png

  實現文件操作集合

  

36.png

  如何填充i2c_msg?

  根據mpu6050的datasheet可知,向mpu6050寫入1個data和讀取1個值的時序分別如下圖所示。

  

37.png

  基于Linux的i2c架構編寫驅動程序,我們需要用struct i2c_msg結構體來表示上述所有信息。

  

38.png

  編寫i2c_msg信息原則如下:

  有幾個S信號,msg數組就要有幾個元素;

  addr為從設備地址,通過i2c總線調用注冊的probe函數的參數i2c_client傳遞下來;

  len的長度不包括S、AD、ACK、P;

  buf為要發送或者要讀取的DATA的內存地址。

  綜上所述:

  Single-Byte Write Sequence時序只需要1個i2c_msg,len值為2,buf內容為是RA、DATA;

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

  

39.png

  I2C內核架構分析

  本章以linux3.14.0為參考, 討論Linux中的i2c控制器驅動是如何實現的。

  驅動入口

  三星的i2c控制器驅動是基于platform總線實現的,struct platform_driver定義如下:

  

40.png

  當設備樹節點信息的compatible信息和注冊的platform_driver.driver. of_match_table字符串會通過platform總線的macth方法進行配對,匹配成功后會調用probe函數s3c24xx_i2c_probe()。

  驅動核心結構

  要理解i2c的內核架構首先必須了解一下這幾個機構體:

  s3c24xx_i2c

  該結構體是三星i2c控制器專用結構體,描述了控制器的所有資源,包括用于等待中斷喚醒的等待隊列、傳輸i2c_msg的臨時指針、記錄與硬件通信的狀態、中斷號、控制器基地址、時鐘、i2c_adapter、設備樹信息pdata等。i2c控制器初始化的時候會為該控制器創建該結構體變量,并初始化之。

  i2c_adapter

  對象實現了一組通過一個i2c控制器發送消息的所有信息, 包括時序, 地址等等, 即封裝了i2c控制器的"控制信息"。它被i2c主機驅動創建, 通過clien域和i2c_client和i2c_driver相連, 這樣設備端驅動就可以通過其中的方法以及i2c物理控制器來和一個i2c總線的物理設備進行交互。

  i2c_algorithm

  描述一個i2c主機的發送時序的信息,該類的對象algo是i2c_adapter的一個域,其中注冊的函數master_xfer()最終被設備驅動端的i2c_transfer()回調。

  i2c_msg

  描述一個在設備端和主機端之間進行流動的數據, 在設備驅動中打包并通過i2c_transfer()發送。相當于skbuf之于網絡設備,urb之于USB設備。

  這幾個結構體之間關系:

  

41.png

  i2c_client

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

  i2c_driver

  描述一個掛接在硬件i2c總線上的設備的驅動方法,即i2c設備的驅動對象,通過i2c_bus_type和設備信息i2c_client匹配,匹配成功后通過clients和i2c_client對象以及i2c_adapter對象相連。

  

42.png

  如上圖所示:Linux內核維護了i2c bus總線,所有的i2c從設備信息都會轉換成i2c_client,并注冊到i2c總線,沒有設備的情況下一般填寫在一下文件中:

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

  

43.png

  內核啟動會將i2c_board_info結構體轉換成i2c_client。

  有設備樹的情況下,內核啟動會自動將設備樹節點轉換成i2c_client。

  i2c_adapter

  我首先說i2c_adapter, 并不是編寫一個i2c設備驅動需要它, 通常我們在配置內核的時候已經將i2c控制器的設備信息和驅動已經編譯進內核了, 就是這個adapter對象已經創建好了, 但是了解其中的成員對于理解i2c驅動框架非常重要, 所有的設備驅動都要經過這個對象的處理才能和物理設備通信

  //include/linux/i2c.h

  

44.png

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

  436-->表示這個一個device, 會掛接到內核中的鏈表中來管理, 其中的

  443-->這個節點將一個i2c_adapter對象和它所屬的i2c_client對象以及相應的i2c_driver對象連接到一起

  下面是2個i2c-core.c提供的i2c_adapter直接相關的操作API, 通常也不需要設備驅動開發中使用。

  Adapter初始化

  i2c控制器設備樹節點信息通過platform總線傳遞下來,即參數pdev。probe函數主要功能是初始化adapter,申請i2c控制器需要的各種資源,同時通過設備樹節點初始化該控制器下的所有從設備,創建i2c_client結構體。

  static int   s3c24xx_i2c_probe(struct platform_device *pdev)

  {

  struct s3c24xx_i2c *i2c;//最重要的結構體

  //保存設備樹信息

  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;

  }

  }

  /*為結構體變量i2c分配內存*/

  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如果采用輪訓方式與底層硬件通信值為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;

  //初始化等待隊列,該等待隊列用于喚醒讀寫數據的進程

  init_waitqueue_head(&i2c->wait);

  /* find the clock and enable it */

  i2c->dev = &pdev->dev;

  //獲取時鐘

  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 */

  //通過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 */

  /*將結構體變量i2c保存到i2c_adapter的私有變量指針algo_data,

  編寫i2c設備驅動可以通過adapter指針找到結構體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復用的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);

  /*將從設備地址寫入寄存器S3C2410_IICADD,同時初始化時鐘頻率*/

  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 (?。╥2c->quirks & QUIRK_POLL))   {

  /*從plat_device中獲得中斷號*/

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

  if (ret <= 0) {

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

  return ret;

  }

  /*注冊中斷處理函數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控制器的通道號,本例是bus 5*/

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

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

  //注冊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;

  }

  老版本的注冊函數為i2c_add_adapter()新的版本對該函數做了封裝,將i2c控制的通道號做了注冊,默認情況下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;

  //設置adapter名字,本例注冊后會生成以下節點/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,通常這兩個引腳會復用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 */

  /*通過設備樹節點注冊所有該控制器下的所有從設備*/

  of_i2c_register_devices(adap);

  acpi_i2c_register_devices(adap);

  /*與動態分配的總線號相關,動態分配的總線號應該是從已經現有最大總線號基礎上+1的,

  這樣能夠保證動態分配出的總線號與板級總線號不會產生沖突

  在沒有設備樹情況下,會基于隊列__i2c_board_list, 創建i2c_client

  其中節點struct i2c_board_info手動填寫*/

  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;

  }

  該函數用于將從設備節點轉換成i2c_client,并注冊到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;

  }

  /*獲取從設備的地址*/

  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;

  }

  /*存儲從設備地址*/

  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;

  }

  /*獲取中斷號*/

  info.irq =   irq_of_parse_and_map(node, 0);

  info.of_node =   of_node_get(node);

  info.archdata = &dev_ad;

  /*獲取設備樹節點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轉換成i2c_client并注冊到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轉換成i2c_client并注冊到Linux核心。

  {

  struct i2c_client      *client;

  int                 status;

  /*給i2c_client分配內存*/

  client = kzalloc(sizeof *client, GFP_KERNEL);

  if (!client)

  return NULL;

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

  在驅動函數中可以通過i2c_client找到adapter*/

  client->adapter = adap;

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

  if (info->archdata)

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

  /*保存從設備地址類型*/

  client->flags = info->flags;

  /*保存從設備地址*/

  client->addr = info->addr;

  /*保存從設備中斷號*/

  client->irq = info->irq;

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

  /* Check for address validity */

  /*檢測從設備地址是否合法,主要檢查位數*/

  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 */

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

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

  if (status)

  goto out_err;

  /*建立從設備與適配器的父子關系*/

  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);

  /*注冊到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核心提供給設備驅動的發送方法, 通過它發送的數據需要被打包成i2c_msg, 這個函數最終會回調相應i2c_adapter->i2c_algorithm->master_xfer()接口將i2c_msg對象發送到i2c物理控制器,

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

  

45.png

  該變量定義如下:

  

46.png

  i2c_transfer()最終會調用函數s3c24xx_i2c_xfer();

  以下是一次i2c_msg傳輸的中斷模式的大概步驟:

  

47.png

  1. i2c_transfer()首先通過函數i2c_trylock_adapter()嘗試獲得adapter的控制權。如果adapter正在忙則返回錯誤信息;

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

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

  4. s3c24xx_i2c_doxfer()通過函數     s3c24xx_i2c_message_start(i2c, msgs)產生S和AD+W的信號,然后通過函數wait_event_timeout( )阻塞在等待隊列i2c->wait上;

  5. 右上角時序mpu6050的寫和讀的時序,從設備回復ACK和DATA都會發送中斷信號給CPU。每次中斷都會調用s3c24xx_i2c_irq->i2c_s3c_irq_nextbyte,

  6. 最后一次中斷,所有數據發送或讀取完畢會調用s3c24xx_i2c_stop->s3c24xx_i2c_master_complete,通過wake_up喚醒阻塞在等待隊列i2c->wait上的任務。

  詳細的代碼流程如下:

  

48.png

  i2c_transfer()首先通過函數i2c_trylock_adapter()嘗試獲得adapter的控制權。如果adapter正在忙則返回錯誤信息;

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

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

  s3c24xx_i2c_doxfer()通過函數     s3c24xx_i2c_message_start(i2c, msgs)產生S和AD+W的信號,然后通過函數wait_event_timeout()阻塞在等待隊列i2c->wait上;

  右上角時序mpu6050的寫和讀的時序,從設備回復ACK和DATA都會發送中斷信號給CPU。每次中斷都會調用s3c24xx_i2c_irq->i2c_s3c_irq_nextbyte,

  最后一次中斷,所有數據發送或讀取完畢會調用s3c24xx_i2c_stop->s3c24xx_i2c_master_complete,通過wake_up喚醒阻塞在等待隊列i2c->wait上的任務。

  詳細的代碼流程如下:

  

49.png

  對著可以根據上圖代碼行號一步步去跟代碼,涉及到寄存器設置可以參考第一章的寄存器使用截圖。



更多信息可以來這里獲取==>>電子技術應用-AET<<

圖片.jpg


本站內容除特別聲明的原創文章之外,轉載內容只為傳遞更多信息,并不代表本網站贊同其觀點。轉載的所有的文章、圖片、音/視頻文件等資料的版權歸版權所有權人所有。本站采用的非本站原創文章及圖片等內容無法一一聯系確認版權者。如涉及作品內容、版權和其它問題,請及時通過電子郵件或電話通知我們,以便迅速采取適當措施,避免給雙方造成不必要的經濟損失。聯系電話:010-82306118;郵箱:[email protected]。
主站蜘蛛池模板: 国产成人欧美一区二区三区的 | 美国毛片毛片全部免费 | 精品国产91久久久久 | 亚洲欧美日韩精品在线 | 欧美午夜不卡在线观看最新 | 欧美一区亚洲二区 | 黄色三区 | 一级毛片一级毛片a毛片欧美 | 在线播放 亚洲 | 久久er国产精品免费观看1 | 中国国语毛片免费观看视频 | 日本高清视频在线观看 | 欧美日韩一区二区在线视频 | 欧做爰xxxⅹ性欧美大 | 国产精品久久久天天影视香蕉 | 成人亚洲国产精品久久 | 欧美成人 综合网播九公社 欧美成人26uuu欧美毛片 | 中文国产成人精品久久一 | 欧美做a一级视频免费观看 欧美做爱毛片 | 午夜香蕉成视频人网站高清版 | 国产人成亚洲第一网站在线播放 | 欧美三级色 | 日韩在线视频不卡一区二区三区 | 午夜影院h| 亚洲免费视频网址 | 欧美精品v日韩精品v国产精品 | www中文字幕在线观看 | 波多野结衣一区二区三区高清在线 | 毛片大全在线观看 | 欧美美女视频网站 | 国产美女毛片 | 国产一区二 | 一本久道综合久久精品 | 国产一区二区三区四区五区 | 日韩欧美一级毛片在线 | 国产一级做a爰片久久毛片99 | 久草在线色站 | 福利三区| 在线精品国产三级 | 国产成人18黄网站在线观看网站 | 黄色欧美视频 |