從IIC通信原理到使用 —— MPU6050

IIC通信詳解 —— 基於MPU6050模塊

從IIC通信原理到使用 —— MPU6050

IIC通信可以簡單地理解成就是數據的通信,就是單片機(主機)與設備(從機)之間的一種通信協議,兩者必須遵從這個協議才能正確的相互讀寫數據,它這個通信過程只要兩根線就能給完成,分別是SCL(時鐘線)和SDA(數據線);

這個主機與從機的關係一定要理解好,因爲下一次不一定是單片機,有些從機也可以作爲主機,可以先把主機理解成能夠向其他設備發送指令/命令的一方,比如如果你作爲主機,你是想讀的時候纔去讀的,想寫的時候就去寫,比較主動;而如果你作爲從機,這就由不得你,你只能聽從主機的命令,主機說要讀,你就要把數據給出去,主機說要寫,你就得乖乖被寫

現在假設單片機作爲主機,連接在SDA和SCL的兩條線上的設備均是它的從機,比如下面圖所示:
主從直接的IIC連線示意圖
到這裏我們已經能夠有一個IIC主從設備的概念了,那麼假如單片機想要通過IIC來讀取從設備的信息應該都要做什麼呢?也就是說,整個IIC通信過程應該是怎麼樣子實現的?

一、IIC通信協議

在解釋之前,可以先知道一個這個協議不協議的到底是怎麼實現的,打比喻說只有一個主機一個從機時,大家先定一個協議,如果某根線的電平怎麼怎麼樣,同時另外一根線的電平又怎麼怎麼樣的時候,對應的是要讀?還是要寫?還是其他什麼?上面這個過程也就相當於主機發出的指令,從機通過兩根線的電平狀態來確定主機的指令是什麼。(注意,不要簡單地理解爲 00 01 10 11 四種狀態)。指令是通過兩根線來發送,裏面的數據也是通過那兩根線,所以你至少要定義你什麼時候是指令什麼時候是數據吧?所以就會有協議這種東西。

IIC總線在傳輸數據的過程中一共有三種類型信號(指令),分別爲:開始信號、結束信號和應答信號。這些信號中,起始信號是必需的,結束信號和應答信號,都可以不要。同時我們還要介紹其空閒狀態、數據的有效性、數據傳輸。看到協議不要害怕,我們只需要先知道它內部的原理,至於怎麼來實現,後面再看代碼進行解釋。

①. 空閒狀態
當SDA和SCL兩條信號線同時處於高電平時,規定爲總線的空閒狀態。這種狀態就相當於沒有設備在佔用通信通道,大家各自在自家互不搭理。正常情況下,當系統開始工作,如果主從不通信時就希望它是空閒狀態,也就是說開機後默認是空閒狀態,那麼這兩個高電平可以由上拉電阻來完成。
空閒狀態
②. 開始信號
當SCL爲高期間,SDA由高到低的跳變;啓動信號是一種電平跳變時序信號,而不是一個電平信號。
③. 結束信號
當SCL爲高期間,SDA由低到高的跳變;停止信號也是一種電平跳變時序信號,而不是一個電平信號。
開啓和停止信號
④. 應答信號
發射器 定義要發送數據的一端爲發射器,當主機往從機寫命令時,可以理解成主機作爲發射器,當主機讀取從機數據時,可以理解成從機往主機寫數據,這時候從機就是發射器;
實現過程 發送器每發送一個字節(1byte = 8bit),8個時鐘脈衝用來儲存數據內容,在第9個時鐘脈衝釋放數據線,由接收器反饋一個應答信號。應答信號爲低電平時,規定爲有效應答位(ACK,簡稱應答位),表示接收器已經成功地接收了該字節;應答信號爲高電平時,規定爲非應答位(NACK),一般表示接收器接收該字節沒有成功。
在這裏插入圖片描述
⑤. 數據的有效性
在數據傳送時,SCL爲高電平期間,SDA上的數據必須保持穩定,只有在時鐘線上的信號爲低電平期間,數據線上的高電平或低電平狀態才允許變化。 即:數據在SCL的上升沿到來之前就需準備好。並在在下降沿到來之前必須穩定。

以上就是協議的一些內容,在發送數據時必須得遵守協議,開啓讀取、讀取完成之後一定要發送停止信號來釋放總線。我們可以接着看數據的發送過程是怎麼實現的。

二、通過IIC協議實現收發

因爲在總線上可能會有很多從設備,不止一個,所以主機在發送或者接受數據的時候,必須要知道從機的設備地址(不同設備有自己不同的設備物理地址),相當於如果你要去某個人的家,至少你要先知道他們家的地址。

知道了這個物理地址後,也就相當於主機能在衆多從設備中找到了想要找的從設備,但是一個完整設備意味着它有自己的芯片,有自己的儲存單元,所以主機不僅僅只是要找到這個設備,還要找到該設備裏的主機想要讀寫的存儲單元(寄存器),相當於你要去某個人的家找一個人,你到了他家之後,還要在他衆多的家人中找到你要找的那個人。

當主機確定了從機設備、從機設備的寄存器,就能往裏面進行讀寫數據了,而這個讀寫數據的過程就必須得按照這個IIC協議來完成,這麼說可能還比較抽象。下面先介紹MPU6050,再結合IIC來實現單片機從MPU6050讀取數據。

三、MPU6050的介紹

MPU-60X0由以下幾個關鍵塊和功能組成
1、帶有16位ADC和信號調理的三軸MEMS速率陀螺儀傳感器
2、具有16位ADC和信號調理的三軸MEMS加速度傳感器
3、數字運動處理器(DMP)引擎
4、傳感器數據寄存器
5、FIFO
6、中斷
7、數字輸出溫度傳感器

首先要知道6050的內置模塊。包括一個三軸MEMS(微機電系統(MEMS, Micro-Electro-Mechanical System))陀螺儀、三軸MEMS加速度計,一個數字運動處理引擎(DMP)、還有用於第三方數字傳感器接口的輔助IIC端口,(GY-521上面的xda,xcl),常用於擴展磁力計,當輔助IIC接口連接到一個三軸磁力計上面時,6050能提供一個完整的九軸融合輸出到其主IIC接口上。同時,6050內部還內置了一個可編程的低通濾波器,可用於傳感器數據的濾波。

系統結構圖
通過系統結構圖只需要你大概知道有什麼在裏面就行了。
系統結構圖
接線圖
當不使用磁力計時(使用磁力計時,MPU6050作爲主機去讀取磁力計的數據,然後再以從機的身份向單片機主機發送加速度、陀螺儀和從磁力計讀取的數據),只需要接電源線(5V)、地線、SCL和SDA即可,其他引腳均閒空。
接線圖
關鍵功能介紹
帶有16位ADC和信號調理的三軸MEMS陀螺儀

MPU-60X0由三個獨立的振動MEMS速率陀螺儀組成,可檢測旋轉角度X軸,Y軸和Z軸。 當陀螺儀圍繞任何感應軸旋轉時,科里奧利效應就會產生電容式傳感器檢測到的振動。 所得到的信號被放大,解調和濾波產生與角速度成比例的電壓。 該電壓使用單獨的片內數字化16位模數轉換器(ADC)對每個軸進行採樣。 陀螺儀傳感器可以全面範圍的被數字編程爲每秒±250,±500,±1000或±2000度(dps)。 ADC樣本速率可以從每秒8,000個採樣點編程到每秒3.9個採樣點,並且可由用戶選擇低通濾波器可實現廣泛的截止頻率。

這就是指模塊內部自帶了陀螺儀,經其內部的數模轉換器可以將陀螺儀的模擬量轉成可傳輸的數字量,並且存儲在16位寄存器裏(由兩個8位寄存器組成),關於獲取陀螺儀數據時,只需直接從對應的寄存器裏讀出數據即可,注意此時的數據並非是傾角。

簡而言之,陀螺儀就是角速度檢測儀。比如,一塊板,以X軸爲軸心,在一秒鐘的時間轉到了90度,那麼它在X軸上的角速度就是 90度/秒 (DPS, 角速度單位,Degree Per Second的縮寫°/S ,體現了轉動的快慢)

具有16位ADC和信號調理的三軸MEMS加速度計

MPU-60X0的3軸加速度計爲每個軸使用單獨的檢測質量。 加速沿着一條特定軸在相應的檢測質量上引起位移,並且電容式傳感器檢測到該位移位移有差別。 MPU-60X0的架構降低了加速度計的敏感度製造變化以及熱漂移。 當設備放置在平坦的表面上時,將進行測量在X和Y軸上爲 0g,在Z軸上爲+ 1g。 加速度計的比例因子在工廠進行校準並且在名義上與電源電壓無關。 每個傳感器都有一個專用的sigma-delta ADC來提供數字輸出。 數字輸出的滿量程範圍可以調整到±2g,±4g,±8g或±16g。

總而言這,加速度傳感器,其實是力傳感器。用來檢查上下左右前後哪幾個面都受了多少力(包括重力),然後計算角度。

數字運動處理器(DMP)

DMP就是指MPU6050內部集成的處理單元,可以直接運算出四元數和姿態,而不再需要另外進行數學運算。四元數就是4個數,可以表徵姿態,經過幾個數學公式之後就可以的出姿態,姿態包括pitch,roll,yaw。DMP的使用大大簡化了代碼設計,可想而知mpu9150(mpu6050)並不單單是一款傳感器,其內部還包含了可以獨立完成姿態解算算法的處理單元。
由DMP實現姿態解算算法將單片機從算法處理的壓力中解放出來,單片機所要做的是等待DMP解算完成後產生的外部中斷,在外部中斷裏去讀取姿態解算的結果。這樣單片機有大量的時間來處理其他任務,提高了系統的實時性。綜上,dmp之後直接出結果,可以直接用,當然你如果有特殊需要自己還要加濾波也沒有問題。

但是聽說DMP的參考平面有點蛋疼。他解算出來的姿態角是以上電時的平面爲基準平面的。也就是說每次上電都要把裝置擺到絕對水平。但這有點困難。

傳感器數據寄存器

傳感器數據寄存器包含最新的陀螺儀,加速度計,輔助傳感器和溫度測量數據。 它們是隻讀寄存器,可通過串行接口訪問。 這些寄存器的數據可以隨時讀取。 但是,可以使用中斷函數來確定新數據何時可用。

存儲這些數據的寄存器均只是可讀狀態,稱之爲數據寄存器,注意:關於陀螺儀和加速度的寄存器均是16位的,也就是說在讀取他們數據時,需要知道加速度/陀螺儀對應的兩個寄存器的內部地址,並且讀取出來的數據需要通過轉換合成。
陀螺儀數據寄存器
上圖是陀螺儀數據寄存器,通過讀取這6個寄存器,就可以讀到陀螺儀 x/y/z 軸的值,比如 x 軸的數據,可以通過讀取 0X43(高 8 位)和 0X44(低 8 位)寄存器得到,其他軸以此類推。
加速度數據寄存器
上圖是加速度數據寄存器,通過讀取這6個寄存器,就可以讀到加速度傳感器 x/y/z 軸的值,比如讀 x 軸的數據,可以通過讀取 0X3B(高 8 位)和0X3C(低8位)寄存器得到,其他軸以此類推。

關於其他寄存器的設置及介紹
我是從這篇帖子裏學習的,我已經碼出來分享給大家
--------》 傳送門
其他寄存器的配置
知道以上寄存器很重要,要知道寄存器每個位什麼值對應着什麼樣的功能,還要知道其對應的內存地址,以供主機進行尋找。

FIFO緩衝器

MPU-60X0包含一個可通過串行接口訪問的1024字節FIFO寄存器。 FIFO配置寄存器決定哪個數據寫入FIFO。 可能的選擇包括陀螺儀數據,加速計數據,溫度讀數,輔助傳感器讀數和 FSYNC 輸入。 FIFO 計數器跟蹤 FIFO 中包含的有效數據字節數。 FIFO寄存器支持突發讀取。 中斷功能可用於確定新數據何時可用。

中斷

中斷功能通過中斷配置寄存器進行配置。 可配置的項目包括INT引腳配置,中斷鎖存和清除方法以及中斷觸發器。 可觸發中斷的項目有:
(1)時鐘發生器鎖定到新的參考振盪器(用於切換時鐘源);
(2)可以讀取新數據(來自FIFO和數據寄存器);
(3)加速度計事件中斷;
(4)MPU-60X0 沒有收到輔助傳感器的確認I2C總線。

中斷狀態可以從中斷狀態寄存器讀取。

至此,瞭解了MPU6050的一些基礎知識後,我們就可以通過代碼建立IIC通信,讓單片機與MPU6050進行通信。

四、單片機與MPU6050的IIC實現

在貼代碼之前,需要明確以下幾點:

A. 關於地址
---- 從設備的地址是用7bit來表示的,但是一個寄存器裏有8個位,所以最終這個設備的地址是由已知的7位和第8位組成,那麼第8位到底是什麼呢?

規定在IIC通信開始發送一幀數據時,前面7bit是設備或者寄存器的地址,第8位則是讀或者寫的方向命令位,0爲寫命令,1爲讀命令。

就比如從官方所給資料來看,MPU6050的地址是0x68或者0x69,拿0x68舉例,其表示如下:

7bit 6bit 5bit 4bit 3bit 2bit 1bit 0bit
1 1 0 1 0 0 0 AD0

前面7位(110 1000)構成從設備的地址0x68,而最後一位是未知的,或者說,是由用戶自己定義設置的(AD0引腳),比如如果在MPU6050模塊的AD0引腳接地,則最後一位表示0,那麼加進去以後,地址就變成了1101 0000,轉換成十六進制也就是0xD0,此處也代表寫命令。

所以以後在主機尋找MPU6050這個設備時,需要寫入一幀數據以表示尋找從機,這一幀數據就是0xD0,說明白點,0xD0是由設備物理地址+命令來構成最終的“門牌”,主機以後就是通過這個門牌來找到MPU6050,主機只要把“門牌”發送出去,系統的狀態就代表着主機向從機寫數據,一直持續到停止信號。

與設備物理地址不同,設備內部的寄存器地址是由8位構成,因此只需要在開啓信號發起時,發送尋找MPU6050的“門牌”(相當於告訴了設備主機打算寫入數據,只要還沒有停止信號就代表後面的操作一直都是寫信號),接着就可以繼續發送寄存器的內存地址(8位地址,最後一位不再是表示讀寫命令,因爲停止信號還沒到來,所以狀態一直是寫的命令)。


B. 關於通信過程

主機寫數據給從機時,先“廣播”從機地址(相當於找到對應從機),再發送從機內部寄存器的地址(相當於找到對應從機的對應寄存器),然後主機再發送數據到該寄存器,主機發送完一字節數據後,從機需要發出一個應答信號(這個過程是從機把應答信號發送至主機的CY位)來告訴主機已接收完成一幀數據。

主機讀從機數據時,要先"廣播"從機地址,再發送從機內部寄存器的地址(相當於要找到數據的來源),然後直接讀取一字節數據,讀取完一字節數據後,主機向從機發送應答信號(相當於主機告訴從機已經讀取到一字節)

綜上,不管是從機還是主機,只要是被寫入數據,當被寫入完一字節數據時,都需要向對方發送應答信號。(相當於讀別人的數據,這個過程比如:可以把主機給從機寫數據的過程理解成從機讀取主機的數據,同樣,主機讀取從機數據時可以理解成從機往主機裏些數據)


代碼實現及註釋

STC89C52 + MPU6050 + LCD1602.

// 功能: 顯示加速度計和陀螺儀的10位原始數據
//****************************************
// 晶振:11.0592M
// 顯示:LCD1602

#include <REG52.H>  
#include <math.h>    //Keil library  
#include <stdio.h>   //Keil library 
#include <INTRINS.H>  //這個庫需要_nop_()函數

typedef unsigned char  uchar; //類型定義
typedef unsigned short ushort;
typedef unsigned int   uint;

#define DataPort P0         //LCD1602數據端口
sbit    SCL=P1^0;           //IIC時鐘引腳定義
sbit    SDA=P1^1;           //IIC數據引腳定義
sbit    LCM_RS=P2^0;        //LCD1602命令端口       
sbit    LCM_RW=P2^1;        //LCD1602命令端口       
sbit    LCM_EN=P2^2;        //LCD1602命令端口
uchar dis[4];               //顯示數字(-511至512)的字符數組
int dis_data;               //變量

//看以下變量的時候對應着寄存器裏面名稱,容易有印象
#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    //X軸加速度數據寄存器1地址
#define ACCEL_XOUT_L    0x3C    //X軸加速度數據寄存器2地址
#define ACCEL_YOUT_H    0x3D    //Y軸加速度數據寄存器1地址
#define ACCEL_YOUT_L    0x3E    //Y軸加速度數據寄存器2地址
#define ACCEL_ZOUT_H    0x3F    //Z軸加速度數據寄存器1地址
#define ACCEL_ZOUT_L    0x40    //Z軸加速度數據寄存器2地址
#define TEMP_OUT_H      0x41    //溫度傳感器數據寄存器1地址
#define TEMP_OUT_L      0x42    //溫度傳感器數據寄存器2地址
#define GYRO_XOUT_H     0x43    //X軸陀螺儀數據寄存器1地址
#define GYRO_XOUT_L     0x44    //X軸陀螺儀數據寄存器2地址    
#define GYRO_YOUT_H     0x45    //Y軸陀螺儀數據寄存器1地址
#define GYRO_YOUT_L     0x46    //Y軸陀螺儀數據寄存器2地址
#define GYRO_ZOUT_H     0x47    //Z軸陀螺儀數據寄存器1地址
#define GYRO_ZOUT_L     0x48    //Z軸陀螺儀數據寄存器2地址
#define PWR_MGMT_1      0x6B    //電源管理,典型值:0x00(正常啓用)
#define SlaveAddress    0xD0    //IIC寫入時的地址字節數據,+1爲讀取
//#define WHO_AM_I        0x75    //IIC地址寄存器(默認數值0x68,只讀)

//****************************************
//函數聲明
//****************************************
void  delay(unsigned int k);   //延時
void  InitLcd();     //初始化lcd1602
void  lcd_printf(uchar *s,int temp_data); //把整數轉成字符串
void  WriteDataLCM(uchar dataW);         //向LCD寫入數據
void  WriteCommandLCM(uchar CMD,uchar Attribc);  //LCD指令
void  DisplayOneChar(uchar X,uchar Y,uchar DData); //顯示一個字符
void  DisplayListChar(uchar X,uchar Y,uchar *DData,L); //顯示字符串
void  InitMPU6050();    //初始化MPU6050
void  Delay5us();   //IIC協議用到的一些延遲
void  I2C_Start(); //開始信號
void  I2C_Stop();  //停止信號
void  I2C_SendACK(bit ack);  //單片機向MPU6050發送應答
bit   I2C_RecvACK();   //單片機接收MPU6050應答
void  I2C_SendByte(uchar dat);   //通過IIC,單片機向從機發送一字節數據
uchar I2C_RecvByte();  //通過IIC,單片機接收從機一字節數據
void  I2C_ReadPage();  
void  I2C_WritePage();
void  display_ACCEL_x(); //LCD顯示X軸加速度信息
void  display_ACCEL_y(); //LCD顯示Y軸加速度信息
void  display_ACCEL_z(); //LCD顯示Z軸加速度信息
uchar Single_ReadI2C(uchar REG_Address);    //讀取I2C數據
void  Single_WriteI2C(uchar REG_Address,uchar REG_data); //向I2C寫入數據


//****************************************
//lcd相關
//****************************************
void lcd_printf(uchar *s,int temp_data)  //整數轉字符串
{
    if(temp_data<0)
    {
        temp_data=-temp_data;
        *s='-';
    }
    else *s=' ';
    *++s =temp_data/100+0x30;
    temp_data=temp_data%100;     //取餘運算
    *++s =temp_data/10+0x30;     //轉成ASCII碼
    temp_data=temp_data%10;      //取餘運算
    *++s =temp_data+0x30;   
}

void delay(unsigned int k)  //延遲函數
{                       
    unsigned int i,j;               
    for(i=0;i<k;i++)
    {           
        for(j=0;j<121;j++);
    }                       
}

void WaitForEnable(void)    //LCD1602寫允許
{                   
    DataPort=0xff;      
    LCM_RS=0;LCM_RW=1;_nop_();
    LCM_EN=1;_nop_();_nop_();
    while(DataPort&0x80);   
    LCM_EN=0;               
}  

void WriteCommandLCM(uchar CMD,uchar Attribc)  //LCD1602寫入命令
{                   
    if(Attribc)WaitForEnable(); 
    LCM_RS=0;LCM_RW=0;_nop_();
    DataPort=CMD;_nop_();   
    LCM_EN=1;_nop_();_nop_();LCM_EN=0;
} 

void WriteDataLCM(uchar dataW)  //LCD1602寫入數據
{                   
    WaitForEnable();        
    LCM_RS=1;LCM_RW=0;_nop_();
    DataPort=dataW;_nop_(); 
    LCM_EN=1;_nop_();_nop_();LCM_EN=0;
}

void DisplayOneChar(uchar X,uchar Y,uchar DData)  //LCD1602寫入一個字符
{                       
    Y&=1;                       
    X&=15;                      
    if(Y)X|=0x40;                   
    X|=0x80;            
    WriteCommandLCM(X,0);       
    WriteDataLCM(DData);        
} 

void DisplayListChar(uchar X,uchar Y,uchar *DData,L)  //LCD1602顯示字符串
{
    uchar ListLength=0; 
    Y&=0x1;                
    X&=0xF;                
    while(L--)             
    {                       
        DisplayOneChar(X,Y,DData[ListLength]);
        ListLength++;  
        X++;                        
    }    
}

void InitLcd()   //LCD1602初始化           
{           
    WriteCommandLCM(0x38,1);  //設置8位格式,2行,5x7  
    WriteCommandLCM(0x08,1);  //關閉顯示,檢測忙信號  
    WriteCommandLCM(0x01,1);  //清除屏幕顯示  
    WriteCommandLCM(0x06,1);  //設定輸入方式,增量不移位  
    WriteCommandLCM(0x0c,1);  //整體顯示,關光標,不閃爍
    DisplayOneChar(0,0,'A');  //顯示A字符在第0行第0位
    DisplayOneChar(0,1,'G');  //總共有0,1兩行,每行0~15個字符
} 


//****************************************
//IIC相關
//****************************************
void Delay5us()  //延時5微秒(STC90C52RC@12M)
{
    _nop_();_nop_();_nop_();_nop_();
    _nop_();_nop_();_nop_();_nop_();
    _nop_();_nop_();_nop_();_nop_();
    _nop_();_nop_();_nop_();_nop_();
    _nop_();_nop_();_nop_();_nop_();
    _nop_();_nop_();_nop_();_nop_();
}

void I2C_Start()  //I2C起始信號
{
    SDA = 1;                    //拉高數據線
    SCL = 1;                    //拉高時鐘線
    Delay5us();                 //延時
    SDA = 0;                    //產生下降沿
    Delay5us();                 //延時
    SCL = 0;                    //拉低時鐘線
}

void I2C_Stop()   //I2C停止信號
{
    SDA = 0;                    //拉低數據線
    SCL = 1;                    //拉高時鐘線
    Delay5us();                 //延時
    SDA = 1;                    //產生上升沿
    Delay5us();                 //延時
}

void I2C_SendACK(bit ack)   //I2C發送應答信號,(0:ACK 1:NAK)
{
    SDA = ack;                  //寫應答信號
    SCL = 1;                    //拉高時鐘線
    Delay5us();                 //延時
    SCL = 0;                    //拉低時鐘線
    Delay5us();                 //延時
}

bit I2C_RecvACK()  //I2C接收應答信號
{
    SCL = 1;                    //拉高時鐘線
    Delay5us();                 //延時
    CY = SDA;                   //讀應答信號,存在CY進位位
    SCL = 0;                    //拉低時鐘線
    Delay5us();                 //延時
    return CY;
}

void I2C_SendByte(uchar dat)  //向I2C總線發送一個字節數據
{
    uchar i;
    for (i=0; i<8; i++)         //8位計數器
    {
        dat <<= 1;              //移出數據的最高位
        SDA = CY;               //送數據口
        SCL = 1;                //拉高時鐘線
        Delay5us();             //延時
        SCL = 0;                //拉低時鐘線
        Delay5us();             //延時
    }
    I2C_RecvACK();
}

uchar I2C_RecvByte()  //從I2C總線接收一個字節數據
{
    uchar i;
    uchar dat = 0;
    SDA = 1;                    //使能內部上拉,準備讀取數據,
    for (i=0; i<8; i++)         //8位計數器
    {
        dat <<= 1;
        SCL = 1;                //拉高時鐘線
        Delay5us();             //延時
        dat |= SDA;             //讀數據               
        SCL = 0;                //拉低時鐘線
        Delay5us();             //延時
    }
    return dat;
}

void Single_WriteI2C(uchar REG_Address,uchar REG_data)//向I2C設備寫入一個字節數據
{
    I2C_Start();                  //起始信號
    I2C_SendByte(SlaveAddress);   //發送設備地址+寫信號
    I2C_SendByte(REG_Address);    //內部寄存器地址,這裏的寫信號由上面一行得來
    I2C_SendByte(REG_data);       //內部寄存器數據,
    I2C_Stop();                   //發送停止信號,持續到停止信號
}

uchar Single_ReadI2C(uchar REG_Address)//從I2C設備讀取一個字節數據
{
    uchar REG_data;
    I2C_Start();                   //起始信號
    I2C_SendByte(SlaveAddress);    //發送設備地址+寫信號
    I2C_SendByte(REG_Address);     //發送存儲單元地址,從0開始
    //上面先進行寫命令(廣播),找到寄存器位置先(數據的來源)  
    I2C_Start();                   //起始信號
    I2C_SendByte(SlaveAddress+1);  //發送設備地址+讀信號
    REG_data=I2C_RecvByte();       //讀出寄存器數據
    I2C_SendACK(1);                //向從機發送應答信號
    I2C_Stop();                    //停止信號
    return REG_data;
}

void InitMPU6050()  //初始化MPU6050
{
    Single_WriteI2C(PWR_MGMT_1, 0x00);  //解除休眠狀態
    Single_WriteI2C(SMPLRT_DIV, 0x07);  //設置陀螺儀採樣率,典型值:0x07(125Hz)
    Single_WriteI2C(CONFIG, 0x06);      //低通濾波頻率,典型值:0x06(5Hz)
    Single_WriteI2C(GYRO_CONFIG, 0x18); //陀螺儀自檢及測量範圍,典型值:0x18(不自檢,2000deg/s)
    Single_WriteI2C(ACCEL_CONFIG, 0x01);//加速計自檢、測量範圍及高通濾波頻率,典型值:0x01(不自檢,2G,5Hz)
}


//****************************************
//主程序相關
//****************************************
int GetData(uchar REG_Address) //將MPU6050內部的兩個數據寄存器的數據進行合成
{
    char H,L;
    H=Single_ReadI2C(REG_Address);
    L=Single_ReadI2C(REG_Address+1);
    return (H<<8)+L;   //合成數據,返回十六進制數
}

void Display10BitData(int value,uchar x,uchar y)//在1602上顯示10位數據
{
    value /= 64;                          //轉換爲10位數據
    lcd_printf(dis, value);         //轉換數據顯示,dis是dis[4]數組的頭地址
    DisplayListChar(x,y,dis,4); //啓始列,行,顯示數組,顯示長度
}

void main()
{ 
    delay(500);     //上電延時      
    InitLcd();      //液晶初始化
    InitMPU6050();  //初始化MPU6050
    delay(150);
    while(1)
    {
        Display10BitData(GetData(ACCEL_XOUT_H),2,0);    //顯示X軸加速度,第0行第2列
        Display10BitData(GetData(ACCEL_YOUT_H),7,0);    //顯示Y軸加速度
        Display10BitData(GetData(ACCEL_ZOUT_H),12,0);   //顯示Z軸加速度
        Display10BitData(GetData(GYRO_XOUT_H),2,1); //顯示X軸角速度
        Display10BitData(GetData(GYRO_YOUT_H),7,1); //顯示Y軸角速度
        Display10BitData(GetData(GYRO_ZOUT_H),12,1);    //顯示Z軸角速度
        delay(500);
    }
}

直接複製以上代碼,即可成功運行。
如有理解錯誤,往大家多多指正,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章