詳解基于Cortex-A9,I2C外設
本文基于三星Cortex-A9架構(gòu),Exynos4412講解I2C原理、以及基于I2C的mpu6050陀螺儀的數(shù)據(jù)讀取實例(包括在裸機模式下數(shù)據(jù)的讀取以及基于Linux驅(qū)動的讀取)。還會分析Linux內(nèi)核I2C架構(gòu),篇幅過長,絕對干貨。
裸機篇
本篇首先詳細講解I2C時序,然后講解如何基于三星I2C控制實現(xiàn)裸機讀取從設備信息方法。
前言
I2C(Inter-Integrated Circuit)總線(也稱 IIC 或 I2C) 是有PHILIPS公司開發(fā)的兩線式串行總線,用于連接微控制器及外圍設備,是微電子通信控制領(lǐng)域廣泛采用的一種總線標準。它是同步通信的一種特殊形式,具有接口線少、控制方式簡單、器件封裝形式小、通信速率較高等優(yōu)點。

Exynos4412 i2c控制器綜述
Exynos4412精簡指令集微處理器支持4個IIC總線控制器。為了能使連接在總線上的主和從設備之間傳輸數(shù)據(jù),專用的數(shù)據(jù)線SDA和時鐘信號線SCL被使用,他們都是雙向的。
如果工作在多主機的IIC總線模式,多個4412處理器將從從機那接收數(shù)據(jù)或發(fā)送數(shù)據(jù)給從機。在IIC總線上的主機端4412會啟動或終止一個數(shù)據(jù)傳輸。4412的IIC總線控制器會用一個標準的IIC總線仲裁機制去實現(xiàn)多主機和多從機傳輸數(shù)據(jù)。
通過控制如下寄存器以實現(xiàn)IIC總線上的多主機操作:
- 控制寄存器: I2CCON
- 狀態(tài)寄存器: I2CSTAT
- Tx/Rx數(shù)據(jù)偏移寄存器:I2CDS
- 地址寄存器: I2CADD
如果I2C總線空閑,那么SCL和SDA信號線將都為高電平。在SCL為高電平期間,如果SDA有由高到低電平的跳變,那么將啟動一個起始信號,如果SDA有由低到高電平的跳變,將啟動一個結(jié)束信號。
主機端的設備總是提供起始和停止信號的一端。在起始信號被發(fā)出后,一個數(shù)據(jù)字節(jié)的前7位被當作地址通過SDA線被傳輸。這個地制值決定了總線上的主設備將要選擇那個從設備作為傳輸對象,bit8決定傳輸數(shù)據(jù)的方向(是讀還是寫)。
I2C總線上的數(shù)據(jù)(即在SDA上傳輸?shù)臄?shù)據(jù))都是以8位字節(jié)傳輸?shù)模诳偩€上傳輸操作的過程中,對發(fā)送或接收的數(shù)據(jù)字節(jié)數(shù)是沒有限制的。I2C總線上的主/從設備發(fā)送數(shù)據(jù)總是以一個數(shù)據(jù)的最高位開始傳輸(即MSB方式),傳輸完一個字節(jié)后,應答信號緊接其后。
Exynos4412 I2C總線接口特性
- 共有9個通道,支持多主、從I2C總線接口。其中8個通道作為普通接口(即I2C0、I2C1....),1個通道作為HDMI的專用接口。
- 7位地址模式。
- 串行,8位單向或雙向的數(shù)據(jù)傳輸。
- 在標準模式中,每秒最多可以傳輸100k位,即12.5kB的數(shù)據(jù)量。
- 在快速模式中,每秒最多可以傳輸400k位,即50kB的數(shù)據(jù)量。
- 支持主機端發(fā)送、接收,從機端發(fā)送、接收操作。
- 支持中斷和查詢方式。
框圖

從上圖可以看出,4412提供4個寄存器來完成所有的IIC操作。SDA線上的數(shù)據(jù)從IICDS寄存器經(jīng)過移位寄存器發(fā)出,或通過移位寄存器傳入IICDS寄器;IICADD寄存器中保存4412當做從機時的地址;IICCON、IICSTAT兩個寄存器用來控制或標識各種狀態(tài),比如選擇工作工作模式,發(fā)出S信號、P信號,決定是否發(fā)出ACK信號,檢測是否接收到ACK信號。
I2C總線接口操作
針對4412處理器的I2C總線接口,具備4種操作模式:
- 主機發(fā)送模式
- 主機接收模式
- 從機發(fā)送模式
- 從機接收模式
下面將描述這些操作模式之間的功能關(guān)系:
0、數(shù)據(jù)有效性

在這里插入圖片描述
SDA線上的數(shù)據(jù)必須在時鐘的高電平周期保持穩(wěn)定。數(shù)據(jù)線的高或低電平狀態(tài)IIC位傳輸數(shù)據(jù)的有效性在SCL線的時鐘信號是低電平才能改變。
1. 開始和停止條件
當4412的I2C接口空閑時,它往往工作在從機模式。或者說,4412的的i2c接口在SDA線上察覺到一個起始信號之前它應該工作在從機模式。當控制器改變4412的i2c接口的工作模式為主機模式后,SDA線上發(fā)起數(shù)據(jù)傳輸并且控制器會產(chǎn)生SCL時鐘信號。
開始條件通過SDA線進行串行的字節(jié)傳輸,一個停止信號終止數(shù)據(jù)傳輸,停止信號是指SCL在高電平器件SDA線有從低到高電平的跳變,主機端產(chǎn)生起始和停止條件。當主、從設備產(chǎn)生一個起始信號后,I2C總線將進入忙狀態(tài)。這里需要說明的是上述主從設備都有可能作為主機端。
當一個主機發(fā)送了一個起始信號后,它也應該發(fā)送一個從機地址以通知總線上的從設備。這個地址字節(jié)的低7位表示從設備地址,最高位表示傳輸數(shù)據(jù)的方向,即主機將要進行讀還是寫。當最高位是0時,它將發(fā)起一個寫操作(發(fā)送操作);當最高位是1時,它將發(fā)起一個讀數(shù)據(jù)的請求(接收操作)。
主機端發(fā)起一個結(jié)束信號以完成傳輸操作,如果主機端想在總線上繼續(xù)進行數(shù)據(jù)的傳輸,它將發(fā)出另外一個起始信號和從設備地址。用這樣的方式,它們可以用各種各樣的格式進行讀寫操作。
下圖為起始和停止信號:

2. 數(shù)據(jù)傳輸格式
放到SDA線上的所有字節(jié)數(shù)據(jù)的長度應該為8位,在每次傳輸數(shù)據(jù)時,對傳輸數(shù)據(jù)量沒有限制。在起始信號后的第一個數(shù)據(jù)字節(jié)應該包含地址字段,當4412的I2C接口被設置為主模式時,地址字節(jié)應該由控制器端發(fā)出。在每個字節(jié)后,應該有一個應答位。
如果從機要完成一些其他功能后(例如一個內(nèi)部中斷服務程序)才能繼續(xù)接收或發(fā)送下一個字節(jié),從機可以拉低SCL迫使主機進入等待狀態(tài)。當從機準備好接收下一個數(shù)據(jù)并釋放SCL后,數(shù)據(jù)傳輸繼續(xù)。如果主機在傳輸數(shù)據(jù)期間也需要完成一些其他功能(例如一個內(nèi)部中斷服務程序)也可以拉低SCL以占住總線。
下面的圖中將說明數(shù)據(jù)傳輸格式:


上圖中說明,在傳輸完每個字節(jié)數(shù)據(jù)后,都會有一個應答信號,這個應答信號在第9個時鐘周期。具體過程如下(注意下面描述的讀寫過程都是針對 4412處理器而言,當有具體的I2C設備與4412相連時,數(shù)據(jù)表示什么需要看具體的I2C設備,4412是不知道數(shù)據(jù)的含義的):
寫過程:主機發(fā)送一個起始信號S→發(fā)送從機7位地址和1位方向,方向位表示寫→主機釋放SDA線方便從機給回應→有從機匹配到地址,拉低SDA線作為ACK→主機重新獲得SDA傳輸8位數(shù)據(jù)→主機釋放SDA線方便從機給回應→從機收到數(shù)據(jù)拉低SDA線作為ACK告訴主機數(shù)據(jù)接收成功→主機發(fā)出停止信號。
讀過程:主機發(fā)送一個起始信號S→發(fā)送從機7位地址和1位方向,方向位表示讀→主機釋放SDA線方便從機給回應→有從機匹配到地址,拉低SDA線作為ACK→從機繼續(xù)占用SDA線,用SDA傳輸8位數(shù)據(jù)給主機→從機釋放SDA線(拉高)方便主機給回應→主機接收到數(shù)據(jù)→主機獲得SDA線控制并拉低SDA線作為ACK告訴從機數(shù)據(jù)接收成功→主機發(fā)出停止信號。
注意:在具體的I2C通信時,要看I2C設備才能確定讀寫時序,比如下面即將描述的第七大點中的示例,讀寫EEPROM中就會說道具體的數(shù)據(jù)含義,讀寫過程。
3. 應答信號的傳輸
為了完成一個字節(jié)數(shù)據(jù)的傳輸,接收方將發(fā)送一個應答位給發(fā)送方。應答信號出現(xiàn)在SCL線上的時鐘周期中的第九個時鐘周期,為了發(fā)送或接收1個字節(jié)的數(shù)據(jù),主機端會產(chǎn)生8個時鐘周期,為了傳輸一個ACK位,主機端需要產(chǎn)生一個時鐘脈沖。ACK時鐘脈沖到來之際,發(fā)送方會在SDA線上設置高電平以釋放SDA線。在ACK時鐘脈沖之間,接收方會驅(qū)動和保持SDA線為低電平,這發(fā)生在第9個時鐘脈沖為高電平期間。應答信號為低電平時,規(guī)定為有效應答位(ACK簡稱應答位),表示接收器已經(jīng)成功地接收了該字節(jié);應答信號為高電平時,規(guī)定為非應答位(NACK),一般表示接收器接收該字節(jié)沒有成功。對于反饋有效應答位ACK的要求是,接收器在第9個時鐘脈沖之前的低電平期間將SDA線拉低,并且確保在該時鐘的高電平期間為穩(wěn)定的低電平。如果接收器是主控器,則在它收到最后一個字節(jié)后,發(fā)送一個NACK信號(即不發(fā)出ACK信號),以通知被控發(fā)送器結(jié)束數(shù)據(jù)發(fā)送,并釋放SDA線,以便主控接收器發(fā)送一個停止信號P。

4. 讀寫操作
當I2C控制器在發(fā)送模式下發(fā)送數(shù)據(jù)后,I2C總線接口將等待直到移位寄存器(I2CDS)接收到一個數(shù)據(jù)。在往此寄存器寫入一個新數(shù)據(jù)前,SCL線應該保持為低電平,寫完數(shù)據(jù)后,I2C控制器將釋放SCL線。當前正在傳輸?shù)臄?shù)據(jù)傳輸完成后,4412會捕捉到一個中斷,然后cpu將開始往I2CDS寄存器中寫入一個新的數(shù)據(jù)。
當I2C控制器在接收模式下接收到數(shù)據(jù)后,I2C總線接口將等待直到I2CDS寄存器被讀。在讀到新數(shù)據(jù)之前,SCL線會被保持為低電平,讀到數(shù)據(jù)后I2C控制器將釋放掉SCL線。一個新數(shù)據(jù)接收完成后,4412將收到一個中斷,cpu收到這個中斷請求后,它將從I2CDS寄存器中讀取數(shù)據(jù)。
5. 總線仲裁機制
總線上可能掛接有多個器件,有時會發(fā)生兩個或多個主器件同時想占用總線的情況,這種情況叫做總線競爭。I2C總線具有多主控能力,可以對發(fā)生在SDA線上的總線競爭進行仲裁,其仲裁原則是這樣的:當多個主器件同時想占用總線時,如果某個主器件發(fā)送高電平,而另一個主器件發(fā)送低電平,則發(fā)送電平與此時SDA總線電平不符的那個器件將自動關(guān)閉其輸出級??偩€競爭的仲裁是在兩個層次上進行的。首先是地址位的比較,如果主器件尋址同一個從器件,則進入數(shù)據(jù)位的比較,從而確保了競爭仲裁的可靠性。由于是利用I2C總線上的信息進行仲裁,因此不會造成信息的丟失。
6. 終止條件
當一個從接收者不能識別從地址時,它將保持SDA線為高電平。在這樣的情況下,主機會產(chǎn)生一個停止信號并且取消數(shù)據(jù)的傳輸。當終止傳輸產(chǎn)生后,主機端接收器會通過取消ACK的產(chǎn)生以告訴從機端發(fā)送器結(jié)束發(fā)送操作。這將在主機端接收器接收到從機端發(fā)送器發(fā)送的最后一個字節(jié)之后發(fā)生,為了讓主機端產(chǎn)生一個停止條件,從機端發(fā)送者將釋放SDA線。
7. 配置I2C總線
如果要設置I2C總線中SCL時鐘信號的頻率,可以在I2CCON寄存器中設置4位分頻器的值。I2C總線接口地址值存放在I2C總線地址寄存器(I2CADD)中,默認值未知。
8. 每種模式下的操作流程圖
在I2C總線上執(zhí)行任何的收發(fā)Tx/Rx操作前,應該做如下配置:
(1)在I2CADD寄存器中寫入從設備地址 (2)設置I2CCON控制寄存器
- a. 使能中斷
- b. 定義SCL頻率
(3)設置I2CSTAT寄存器以使能串行輸出
下圖為主設備發(fā)送模式

下圖為主設備接收模式
下圖為從設備發(fā)送模式

下圖為從設備接收模式

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

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

使用IICCON寄存器時,有如下注意事項
1.發(fā)送模式的時鐘頻率由位[6]、位[3:0]聯(lián)合決定。另外,當 IICCON[6]=0時,IICCON[3:0]不能取0或1。
2.位[4]用來標識是否有I2C中斷發(fā)生,讀出為0時標識沒有中斷發(fā)生,讀出為1時標識有中斷發(fā)生。當此位為1時,SCL線被拉低,此時所以I2C傳輸停止;如果要繼續(xù)傳輸,需寫入0清除它。
中斷在以下3種情況下發(fā)生:
- 當發(fā)送地址信息或接收到一個從機地址并且吻合時;
- 當總線仲裁失敗時;
- 當發(fā)送/接收完一個字節(jié)的數(shù)據(jù)(包括響應位)時;
3.基于SDA、SCL線上時間特性的考慮,要發(fā)送數(shù)據(jù)時,先將數(shù)據(jù)寫入IICDS寄存器,然后再清除中斷。
4.如果IICCON[5]=0,IICCON[4]將不能正常工作,所以,即使不使用I2C中斷,也要將IICCON[5]設為1.
2 -- I2C狀態(tài)寄存器
IICSTAT寄存器用于選擇I2C接口的工作模式,發(fā)出S信號、P信號,使能接收/發(fā)送功能,并標識各種狀態(tài),比如總線仲裁是否成功、作為從機時是否被尋址、是否接收到0地址、是否接收到ACK信號等。

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

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


【注意】上述兩個時序非常重要,下面我們編寫基于linux的驅(qū)動編寫i2c_msg還要再依賴他。
上述簡化時序的術(shù)語解釋如下

【寄存器使用規(guī)則】
下面先提前講一下具體應用中如何啟動和恢復IIC的傳輸 啟動或恢復4412的I2C傳輸有以下兩種方法。1) 當IICCON[4]即中斷狀態(tài)位為0時,通過寫IICSTAT寄存器啟動I2C操作。有以下兩種情況。
- 1--在主機模式, 令I(lǐng)ICSTAT[5:4]等于0b11,將發(fā)出S信號和IICDS寄存器的數(shù)據(jù)(尋址), 令I(lǐng)ICSTAT[5:4]等于0b01,將發(fā)出P信號。
- 2--在從機模式,令I(lǐng)ICSTAT[4]等于1將等待其他主機發(fā)出S信號及地址信息。
2)當IICCON[4]即中斷狀態(tài)為1時,表示I2C操作被暫停。在這期間設置好其他寄存器之后,向IICCON[4]寫入0即可恢復I2C操作。所謂“設置其他寄存器”,有以下三種情況:
- 1--對于主機模式,可以按照上面1的方法寫IICSTAT寄存器,恢復I2C操作后即可發(fā)出S信號和IICDS寄存器的值(尋址),或發(fā)出P信號。
- 2--對于發(fā)送器,可以將下一個要發(fā)送的數(shù)據(jù)寫入IICDS寄存器中,恢復I2C操作后即可發(fā)出這個數(shù)據(jù)。
- 3--對于接收器,可以從IICDS寄存器讀出接收到的數(shù)據(jù)。最后向IICCON[4]寫入0的同時,設置IICCON[7]以決定是否在接收到下一個數(shù)據(jù)后是否發(fā)出ACK信號。
MPU6050
MPU-6000(6050)為全球首例整合性6軸運動處理組件,相較于多組件方案,免除了組合陀螺儀與加速器時間軸之差的問題,減少了大量的封裝空間。當連接到三軸磁強計時,MPU-60X0提供完整的9軸運動融合輸出到其主I2C或SPI端口(SPI僅在MPU-6000上可用)。
MPU-6000(6050)的角速度全格感測范圍為±250、±500、±1000與±2000°/sec (dps),可準確追蹤快速與慢速動作,并且,用戶可程式控制的加速器全格感測范圍為±2g、±4g±8g與±16g。
產(chǎn)品傳輸可透過最高至400kHz的IIC或最高達20MHz的SPI(MPU-6050沒有SPI)。
電路圖
【MPU6050硬件電路圖】(實際板子電路圖不一定和下面一樣,具體問題具體分析,本例參考exynos-fs4412開發(fā)板)

1 AD0接地的 值為 0

所以從設備地址為0x68;
2 SCL、SDA連接的i2c_SCL5、i2c_SDA5

由此可得這兩個信號線復用了GPIO的GPB的2、3引腳;
3 查閱exynos4412 datasheet 6.2.2 Part 1可得

所以設置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多個寄存器。比如:


這個寄存器是用來設置加速度屬性的,當bit[4:3] 設置為0,表示3個軸的加速度量程最大為±2g。
mpu6050的內(nèi)部寄存器非常多,并不需要每一個寄存器都需要搞懂,在如下代碼實例中,我已經(jīng)列舉出常用的寄存器以及他們的典型值,其他的寄存器不再一一介紹。
下面是個IIC總線實例:用IIC總線實現(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 //陀螺儀自檢及測量范圍,典型值: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地址寄存器(默認數(shù)值0x68,只讀)
- #define SlaveAddress 0xD0 //IIC寫入時的地址字節(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í)行寫操作:寫入一個從設備地址,
- 然后執(zhí)行讀操作,才能讀取到該內(nèi)部寄存器的內(nèi)容*/
- I2C5.I2CDS = slave_addr; //將從機地址寫入I2CDS寄存器中
- I2C5.I2CCON = (1 << 7)|(1 << 6)|(1 << 5); //設置時鐘并使能中斷
- I2C5.I2CSTAT = 0xf0; //[7:6]設置為0b11,主機發(fā)送模式;
- //往[5:4]位寫0b11,即產(chǎn)生啟動信號,發(fā)出IICDS寄存器中的地址
- while(!(I2C5.I2CCON & (1 << 4))); // 等待傳輸結(jié)束,傳輸結(jié)束后,I2CCON [4]位為1,標識有中斷發(fā)生;
- // 此位為1時,SCL線被拉低,此時I2C傳輸停止;
- I2C5.I2CDS = addr; //寫命令值
- I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));// I2CCON [4]位清0,繼續(xù)傳輸
- while(!(I2C5.I2CCON & (1 << 4)));// 等待傳輸結(jié)束
- I2C5.I2CSTAT = 0xD0; // I2CSTAT[5:4]位寫0b01,發(fā)出停止信號
- I2C5.I2CDS = slave_addr | 1; //表示要讀出數(shù)據(jù)
- I2C5.I2CCON = (1 << 7)|(1 << 6) |(1 << 5) ; //設置時鐘并使能中斷
- I2C5.I2CSTAT = 0xb0;//[7:6]位0b10,主機接收模式;
- //往[5:4]位寫0b11,即產(chǎn)生啟動信號,發(fā)出IICDS寄存器中的地址
- // I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4)); 如果強行關(guān)閉,將讀取不到數(shù)據(jù)
- while(!(I2C5.I2CCON & (1 << 4)));//等待傳輸結(jié)束,接收數(shù)據(jù)
- I2C5.I2CCON &= ~((1<<7)|(1 << 4));/* Resume the operation & no ack*/
- // I2CCON [4]位清0,繼續(xù)傳輸,接收數(shù)據(jù),
- // 主機接收器接收到最后一字節(jié)數(shù)據(jù)后,不發(fā)出應答信號 no ack
- // 從機發(fā)送器釋放SDA線,以允許主機發(fā)出P信號,停止傳輸;
- while(!(I2C5.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(!(I2C5.I2CCON & (1 << 4)));
- I2C5.I2CDS = addr;
- I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));
- while(!(I2C5.I2CCON & (1 << 4)));
- I2C5.I2CDS = data;
- I2C5.I2CCON = I2C5.I2CCON & (~(1 << 4));
- while(!(I2C5.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某個內(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;
- }
- /*
- * 裸機代碼,不同于LINUX 應用層, 一定加循環(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;
- }
實驗結(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
讀寫操作代碼解析:
寫入一個數(shù)據(jù)流程:

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

上圖閱讀注意點:
- 從設備地址是在用的時候應該左移一位|讀寫位,比如寫reg=0x68<1|0,即0xD0;
- 主設備發(fā)出S信號,需要將I2CSTATn 的bite:5設置為1;
- 主設備發(fā)出p信號,需要將I2CSTATn 的bite:5設置為0;
- 主機發(fā)送數(shù)據(jù)需要將寄存器I2CCONn的bit:4置0,to reume the operation;
- 主機等待從設備發(fā)送的ack或者data,需要輪訓判斷I2CCONn的bit:4是否置1;
- 代碼的理解除了結(jié)合功能流程圖、時序圖、源代碼還要結(jié)合寄存器說明;
- 代碼的編寫順序必須嚴格按照時序和模塊流程圖執(zhí)行;
- 時序中的每一個數(shù)據(jù)信號(包括ack、data、reg)的產(chǎn)生或者發(fā)送對應的代碼都用箭頭以及相同的顏色框處;
- 對1于read操作,NACK的回復需要在接收最后一個data之前設置I2CCONn :7位為0,這樣在收到從設備的data后,才會將SDA拉低。