文章目錄
- 一、I2C通信
- 1.1 I2C
- 1.2硬件電路
- 1.3I2C時序基本單元
- 1.4I2C時序
- 二、MPU6050
- 2.1簡介
- 2.2MPU6050參數
- 2.3硬件電路
- 2.4MPU6050框圖
- 三、I2C外設(硬件)
- 3.1簡介
- 3.2I2C框圖
- 3.3I2C基本結構
- 3.4主機發送
- 3.5主機接收
- 3.6軟件/硬件波形對比
- 1. 時序精度
- 2. 信號穩定性
- 3. 速率與效率
- 4. 波形一致性
- 四、軟件I2C讀寫MPU6050
- 4.1接線圖
- 4.2代碼
- **1. 初始化階段**
- **2. 起始信號(Start Condition)**
- **3. 尋址階段**
- **4. 數據傳輸階段**
- **寫操作(主機→從機)**
- **讀操作(從機→主機)**
- **5. 停止信號(Stop Condition)**
- **6. 完整通信示例(讀 MPU6050 的 WHO_AM_I 寄存器)**
- **7. 關鍵時序細節**
- 1. 配置類寄存器
- 2. 數據輸出寄存器
- 3. 電源與識別寄存器
- 4.3相關API
- 4.4現象
- 五、硬件I2C讀寫MPU6050
- 5.1接線圖
- 5.2代碼
- 5.3相關API
- **1. 核心功能 API 列表**
- **2. 關鍵 API 詳細說明**
- **2.1 MPU6050_Init () - 傳感器初始化**
- **2.2 MPU6050_WriteReg () - 寫寄存器**
- **2.3 MPU6050_ReadReg () - 讀寄存器**
- **2.4 MPU6050_GetData () - 獲取傳感器數據**
- **3. 硬件 I2C 與軟件模擬的差異**
- 5.4現象
- **一、正常運行現象**
一、I2C通信
1.1 I2C
1.2硬件電路
1.3I2C時序基本單元
1.4I2C時序
二、MPU6050
2.1簡介
2.2MPU6050參數
2.3硬件電路
2.4MPU6050框圖
- 傳感器部分:3 軸加速度計(X/Y/Z Accel)、3 軸陀螺儀(X/Y/Z Gyro)、溫度傳感器(Temp Sensor),用于采集運動及環境數據,且各傳感器有自測試(Self test)功能。
- 信號處理:經 ADC(模數轉換器)轉換模擬信號,再由 Signal Conditioning(信號調理)模塊處理 。
- 接口與控制:有多種串行接口(Slave I2C and SPI、Master I2C 等),還有中斷(Interrupt Status Register)、FIFO(先入先出存儲器)、配置寄存器(Config Registers )等,用于數據傳輸、控制和配置,Digital Motion Processor(DMP,數字運動處理器 )可輔助處理運動數據,Bias & LDO 是偏置和低壓差穩壓器相關模塊,保障芯片供電和信號穩定 ,常用于運動檢測、姿態感知等場景,像無人機、穿戴設備里的姿態控制 。
三、I2C外設(硬件)
3.1簡介
3.2I2C框圖
- 信號線路
- SDA(Serial Data Line,串行數據線):雙向數據線,負責在設備間傳輸數據,比如傳感器向主控芯片發送采集的溫濕度信息,或主控芯片向存儲設備寫入指令 。
- SCL(Serial Clock Line,串行時鐘線):由主設備產生時鐘信號,為數據傳輸提供同步時序,像 I2C 總線通信時,主設備通過 SCL 控制數據在 SDA 上的收發節奏 。
- SMBALERT(System Management Bus Alert,系統管理總線報警線 ):可用于設備觸發報警、通知等功能,例如監測芯片檢測到異常電壓時,通過該線向主控發送報警信號 。
- 核心功能模塊
- 數據控制模塊:協調 SDA 線上的數據收發,決定何時發送、接收數據,以及處理數據傳輸中的邏輯,比如判斷數據幀的起始、停止等狀態 。
- 時鐘控制模塊:依據 SCL 線輸入的時鐘信號,管理總線通信時序,同時結合時鐘控制寄存器(CCR)配置,調整時鐘頻率等參數,適配不同設備通信需求 。
- 數據移位寄存器:在數據傳輸時,實現數據的串行 - 并行轉換。發送數據時,將并行數據逐位串行輸出到 SDA;接收數據時,把 SDA 傳來的串行數據轉為并行,存入數據寄存器(DATA REGISTER ) 。
- 比較器:用于地址匹配,將總線上接收到的從設備地址,與自身地址寄存器、雙地址寄存器中存儲的地址對比,判斷是否為目標設備,若匹配則響應通信 。
- 幀錯誤校驗(PEC,Packet Error Checking )計算單元:對傳輸的數據幀進行校驗計算,生成校驗碼,也能對比接收到的 PEC 寄存器內校驗碼,檢測數據傳輸是否出錯,保障數據完整性 。
- 寄存器組
- 自身地址寄存器、雙地址寄存器:存儲 I2C 設備自身的地址信息,支持單地址或雙地址模式,方便主設備尋址,比如一個從設備可設置兩個不同地址,供主設備靈活訪問 。
- 幀錯誤校驗(PEC)寄存器:存放用于錯誤校驗的相關數據,配合 PEC 計算單元,完成數據幀的校驗工作 。
- 時鐘控制寄存器(CCR):配置時鐘相關參數,像設置時鐘分頻系數,調節 SCL 線的時鐘頻率,滿足不同通信速率要求,比如高速模式、標準模式切換 。
- 控制寄存器(CR1&CR2):控制 I2C 總線的工作模式、使能中斷等功能,例如通過 CR1 開啟 I2C 模塊、使能特定中斷,CR2 配置地址模式等 。
- 狀態寄存器(SR1&SR2 ):實時反饋 I2C 總線的工作狀態,如是否檢測到起始信號、數據是否發送完成、有無錯誤發生等,為主控判斷通信進程提供依據 。
- 控制邏輯電路:作為 I2C 總線的 “指揮中心”,整合各模塊工作,根據寄存器配置和總線狀態,調度數據傳輸、時鐘管理、地址識別等操作,還能觸發中斷(如數據收發完成中斷、錯誤中斷 ),向 DMA(直接內存訪問 )發出請求并響應,讓數據高效傳輸,減輕 CPU 負擔,廣泛應用于嵌入式系統中芯片間低速數據交互,像單片機與傳感器、EEPROM 等設備的通信 。
3.3I2C基本結構
- 模塊分工
時鐘控制器:生成或同步 SCL(時鐘線)信號,就像 “節拍器”,規定數據收發的節奏,讓通信雙方按同一節奏干活 。
數據控制器:管數據收發邏輯,決定啥時候發數據、咋接收數據,是數據通信的 “指揮官” 。
移位寄存器:數據 “轉換器”—— 發送時,把 數據寄存器(DR) 里的并行數據,拆成串行一位一位發;接收時,把 SDA 收的串行數據,重新拼成并行存到 DR,適配總線串行傳輸的規則 。
數據寄存器(DR):臨時存數據的 “小倉庫”,發數據時從這取,收數據時存這,方便 CPU 或其他模塊讀寫 。
GPIO(通用輸入輸出):芯片對外的 “手”,把內部時鐘(SCL)、數據(SDA)信號送出去,也能從外部收信號,實現芯片和外部設備(比如傳感器、屏幕 )的連接 。
開關控制:像 “總閘”,可能用來打開 / 關閉 I2C 功能、切換工作模式(比如主設備 / 從設備模式 ),靈活控制總線通斷 。
2.通信流程
比如單片機要給傳感器發指令、收數據:
- 發數據:CPU 把指令放進
DR
→移位寄存器
把并行指令拆成串行 →數據控制器
配合時鐘控制器
的節奏,通過GPIO
從SDA
把指令發出去,SCL
同步發時鐘信號讓傳感器 “對節奏” 。 - 收數據:傳感器通過
SDA
回傳串行數據 →GPIO
收到后,移位寄存器
把串行數據拼成并行存到DR
→ CPU 從DR
里讀數據,完成通信 。
3.4主機發送
3.5主機接收
完整代碼示例(STM32 硬件 I2C 寫操作)
假設使用 I2C1,連接從機地址 0x50(7 位地址,實際發送時會左移 1 位 + 讀寫位),你可根據實際需求修改。
c
運行
#include "stm32f10x.h"// 從機地址(7位),實際通信時會自動左移1位 + 讀寫位
#define I2C_SLAVE_ADDR 0x50 /*** @brief 等待 I2C 事件,對應時序圖的 EVx* @param I2Cx: I2C1/I2C2 外設* @param event: 要等待的事件(如 I2C_EVENT_MASTER_MODE_SELECT)* @retval 無(超時可自行加邏輯,這里簡化處理)*/
void I2C_WaitEvent(I2C_TypeDef* I2Cx, uint32_t event)
{// 簡單超時機制(實際項目建議加更完善的超時處理)uint32_t timeout = 0xFFFF;while (I2C_CheckEvent(I2Cx, event) != SUCCESS){if (timeout-- == 0) break; // 超時退出}
}/*** @brief I2C 初始化(配置為 7 位地址、標準模式 100kHz)* @param 無* @retval 無*/
void I2C_Init_Master(void)
{I2C_InitTypeDef I2C_InitStruct;GPIO_InitTypeDef GPIO_InitStruct;// 1. 使能時鐘:I2C1 + 對應 GPIO 時鐘RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 假設用 PB6(SCL)/PB7(SDA)// 2. 配置 GPIO:復用開漏輸出GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);// 3. 配置 I2CI2C_InitStruct.I2C_Mode = I2C_Mode_I2C; // I2C 模式I2C_InitStruct.I2C_ClockSpeed = 100000; // 100kHz 標準模式I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; // 占空比 2(Tlow/Thigh = 2)I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; // 使能應答I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7 位地址I2C_InitStruct.I2C_OwnAddress1 = 0x00; // 主機自身地址(從機模式才用,這里隨意填)I2C_Init(I2C1, &I2C_InitStruct);// 4. 使能 I2CI2C_Cmd(I2C1, ENABLE);
}/*** @brief 硬件 I2C 寫一個字節到從機寄存器* @param regAddr: 從機內部寄存器地址(如 MPU6050 的 0x6B)* @param data: 要寫入的數據(1 字節)* @retval 無* @note 嚴格對應時序圖 7 位主發送流程:S → 地址+A → 數據1+A → 數據2+A → ... → P*/
void I2C_WriteByte(uint8_t regAddr, uint8_t data)
{// 1. 發送起始條件(S),等待 EV5I2C_GenerateSTART(I2C1, ENABLE);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT); // 等 EV5(SB=1)// 2. 發送從機地址 + 寫方向(7位地址<<1 | 0),等待 EV6I2C_Send7bitAddress(I2C1, I2C_SLAVE_ADDR, I2C_Direction_Transmitter);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); // 等 EV6(ADDR=1)// 3. 發送寄存器地址(數據1),等待 EV8_1I2C_SendData(I2C1, regAddr);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING); // 等 EV8_1(TXE=1, BTF=0)// 4. 發送數據(數據2),等待 EV8_2I2C_SendData(I2C1, data);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED); // 等 EV8_2(TXE=1, BTF=1)// 5. 發送停止條件(P)I2C_GenerateSTOP(I2C1, ENABLE);
}int main(void)
{// 初始化硬件 I2CI2C_Init_Master();// 示例:往從機 0x50 的 0x10 寄存器寫 0xAAI2C_WriteByte(0x10, 0xAA);while (1){// 主循環可做其他事}
}
代碼與時序圖的逐步驟對應
對照你提供的7 位主發送時序圖,每一步代碼都對應時序圖的節點:
時序圖階段 代碼操作 對應事件(EVx) 硬件狀態說明
S(起始) I2C_GenerateSTART(I2C1, ENABLE) EV5 (SB=1) 起始條件發送成功,總線進入忙狀態
地址 + A I2C_Send7bitAddress(…, 寫方向) EV6 (ADDR=1) 從機地址發送完成,收到從機應答
數據 1 + A I2C_SendData(regAddr) EV8_1 (TXE=1, BTF=0) 數據寄存器空,移位寄存器非空(正在發送)
數據 2 + A I2C_SendData(data) EV8_2 (TXE=1, BTF=1) 數據發送完成,移位寄存器和數據寄存器都空
P(停止) I2C_GenerateSTOP(I2C1, ENABLE) - 停止條件發送,總線釋放
EV5:檢測 SR1 寄存器的 SB 位,確保起始信號發出去了。
EV6:檢測 SR1 的 ADDR 位 + SR2 的 TXE 位,確保從機地址發完且收到應答。
EV8_1:檢測 SR1 的 TXE=1 且 BTF=0,說明 “數據寄存器空了,但移位寄存器還在發之前的數據”。
EV8_2:檢測 SR1 的 TXE=1 且 BTF=1,說明 “移位寄存器和數據寄存器都空了,數據發完了”。
擴展:讀操作怎么寫?(對應時序圖 “重復起始”)
如果要實現讀操作(比如先寫寄存器地址,再讀數據),需要重復起始條件(對應時序圖的 “Sr”),核心邏輯如下(簡化示例):
c
運行
uint8_t I2C_ReadByte(uint8_t regAddr)
{uint8_t data;// 1. 先寫寄存器地址(同寫操作前 3 步)I2C_GenerateSTART(I2C1, ENABLE);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT); I2C_Send7bitAddress(I2C1, I2C_SLAVE_ADDR, I2C_Direction_Transmitter);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); I2C_SendData(I2C1, regAddr);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED); // 2. 重復起始條件(Sr),切換為讀方向I2C_GenerateSTART(I2C1, ENABLE);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT); // 3. 發送從機地址 + 讀方向(7位地址<<1 | 1),等待 EV6(讀模式)I2C_Send7bitAddress(I2C1, I2C_SLAVE_ADDR, I2C_Direction_Receiver);I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); // 4. 接收數據(最后一個字節發非應答 + 提前發停止)I2C_AcknowledgeConfig(I2C1, DISABLE); // 最后一個字節不應答I2C_GenerateSTOP(I2C1, ENABLE); // 提前發停止I2C_WaitEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED); // 等 EV7data = I2C_ReceiveData(I2C1); // 讀數據寄存器// 5. 恢復應答(方便下次通信)I2C_AcknowledgeConfig(I2C1, ENABLE); return data;
}
關鍵差異:讀操作需要 “重復起始” 切換方向,且最后一個字節要關閉應答 + 提前發停止,對應時序圖的 “讀模式收尾”。
總結:把時序圖轉代碼的核心思路
拆分時序節點:把時序圖按 S→地址→數據→P 拆成步驟,每個步驟對應一個硬件操作(發起始、發地址、發數據、發停止)。
等待事件(EVx):每個操作后,必須等硬件狀態寄存器(SR1/SR2)的標志位,確保時序圖的 “事件” 完成。
硬件自動處理電平:你不用手動控制 SDA/SCL,調用 I2C_GenerateSTART、I2C_SendData 等函數,硬件會自動翻轉引腳電平,匹配時序圖。
這樣,不管是給 MPU6050 寫寄存器,還是驅動其他 I2C 設備(OLED、EEPROM 等),都可以用這套 “拆分時序節點 + 等待 EVx” 的思路,把時序圖轉化為穩定的硬件 I2C 代碼。
3.6軟件/硬件波形對比
1. 時序精度
- 硬件 I2C:由硬件外設按固定時鐘邏輯生成波形,像 “精準節拍器”,SCL(時鐘線)、SDA(數據線)的電平翻轉嚴格對齊硬件時鐘,時序誤差極小、一致性高 。比如 STM32 的硬件 I2C,能穩定輸出標準模式(100kHz)、快速模式(400kHz+ )的精準時序。
- 軟件模擬 I2C:靠 GPIO 引腳翻轉 + 軟件延時模擬時序,受 CPU 負載、延時函數精度(如普通
delay
函數的誤差 )影響,時序誤差大 。比如模擬 100kHz 速率時,軟件延時的微小偏差會讓 SCL 周期、占空比 “跑偏”,導致波形時序不標準。
2. 信號穩定性
- 硬件 I2C:硬件電路直接驅動總線,輸出能力穩定,SCL/SDA 的電平幅值、邊沿跳變清晰干凈,抗干擾強 。即使總線掛多個設備,硬件驅動也能保障信號質量。
- 軟件模擬 I2C:依賴 GPIO 軟件控制,輸出驅動能力弱(尤其頻繁切換電平,易受總線電容、上拉電阻影響 ),波形易出現 “毛刺”“邊沿變緩” 。比如高電平拉不上去、低電平釋放慢,導致信號識別困難。
3. 速率與效率
- 硬件 I2C:速率上限高(支持標準 100kHz、快速 400kHz,甚至高速模式 ),且硬件自動處理時序,CPU 幾乎不參與 ,能邊通信邊干其他任務,效率拉滿。
- 軟件模擬 I2C:速率受軟件延時、CPU 運算限制,通常難超 100kHz ,且 CPU 得全程 “盯著” GPIO 翻轉,占用大量資源,干不了別的活,效率低。
4. 波形一致性
- 硬件 I2C:同一 MCU 同一配置下,每次通信波形高度一致,像 “復制粘貼”,天生適配標準 I2C 協議。
- 軟件模擬 I2C:受代碼執行環境(如中斷干擾、任務切換 )影響,不同次通信波形可能有差異 ,比如延時被打斷,導致 SCL 周期忽長忽短。
四、軟件I2C讀寫MPU6050
4.1接線圖
4.2代碼
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"uint8_t ID; //定義用于存放ID號的變量
int16_t AX, AY, AZ, GX, GY, GZ; //定義用于存放各個數據的變量int main(void)
{/*模塊初始化*/OLED_Init(); //OLED初始化MPU6050_Init(); //MPU6050初始化/*顯示ID號*/OLED_ShowString(1, 1, "ID:"); //顯示靜態字符串ID = MPU6050_GetID(); //獲取MPU6050的ID號OLED_ShowHexNum(1, 4, ID, 2); //OLED顯示ID號while (1){MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ); //獲取MPU6050的數據OLED_ShowSignedNum(2, 1, AX, 5); //OLED顯示數據OLED_ShowSignedNum(3, 1, AY, 5);OLED_ShowSignedNum(4, 1, AZ, 5);OLED_ShowSignedNum(2, 8, GX, 5);OLED_ShowSignedNum(3, 8, GY, 5);OLED_ShowSignedNum(4, 8, GZ, 5);}
}
MyI2C.c
#include "stm32f10x.h" // Device header
#include "Delay.h"/*引腳配置層*//*** 函 數:I2C寫SCL引腳電平* 參 數:BitValue 協議層傳入的當前需要寫入SCL的電平,范圍0~1* 返 回 值:無* 注意事項:此函數需要用戶實現內容,當BitValue為0時,需要置SCL為低電平,當BitValue為1時,需要置SCL為高電平*/
void MyI2C_W_SCL(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue); //根據BitValue,設置SCL引腳的電平Delay_us(10); //延時10us,防止時序頻率超過要求
}/*** 函 數:I2C寫SDA引腳電平* 參 數:BitValue 協議層傳入的當前需要寫入SDA的電平,范圍0~1* 返 回 值:無* 注意事項:此函數需要用戶實現內容,當BitValue為0時,需要置SDA為低電平,當BitValue為1時,需要置SDA為高電平*/
void MyI2C_W_SDA(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue); //根據BitValue,設置SDA引腳的電平,BitValue要實現非0即1的特性Delay_us(10); //延時10us,防止時序頻率超過要求
}/*** 函 數:I2C讀SDA引腳電平* 參 數:無* 返 回 值:協議層需要得到的當前SDA的電平,范圍0~1* 注意事項:此函數需要用戶實現內容,當前SDA為低電平時,返回0,當前SDA為高電平時,返回1*/
uint8_t MyI2C_R_SDA(void)
{uint8_t BitValue;BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); //讀取SDA電平Delay_us(10); //延時10us,防止時序頻率超過要求return BitValue; //返回SDA電平
}/*** 函 數:I2C初始化* 參 數:無* 返 回 值:無* 注意事項:此函數需要用戶實現內容,實現SCL和SDA引腳的初始化*/
void MyI2C_Init(void)
{/*開啟時鐘*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //開啟GPIOB的時鐘/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure); //將PB10和PB11引腳初始化為開漏輸出/*設置默認電平*/GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); //設置PB10和PB11引腳初始化后默認為高電平(釋放總線狀態)
}/*協議層*//*** 函 數:I2C起始* 參 數:無* 返 回 值:無*/
void MyI2C_Start(void)
{MyI2C_W_SDA(1); //釋放SDA,確保SDA為高電平MyI2C_W_SCL(1); //釋放SCL,確保SCL為高電平MyI2C_W_SDA(0); //在SCL高電平期間,拉低SDA,產生起始信號MyI2C_W_SCL(0); //起始后把SCL也拉低,即為了占用總線,也為了方便總線時序的拼接
}/*** 函 數:I2C終止* 參 數:無* 返 回 值:無*/
void MyI2C_Stop(void)
{MyI2C_W_SDA(0); //拉低SDA,確保SDA為低電平MyI2C_W_SCL(1); //釋放SCL,使SCL呈現高電平MyI2C_W_SDA(1); //在SCL高電平期間,釋放SDA,產生終止信號
}/*** 函 數:I2C發送一個字節* 參 數:Byte 要發送的一個字節數據,范圍:0x00~0xFF* 返 回 值:無*/
void MyI2C_SendByte(uint8_t Byte)
{uint8_t i;for (i = 0; i < 8; i ++) //循環8次,主機依次發送數據的每一位{/*兩個!可以對數據進行兩次邏輯取反,作用是把非0值統一轉換為1,即:!!(0) = 0,!!(非0) = 1*/MyI2C_W_SDA(!!(Byte & (0x80 >> i)));//使用掩碼的方式取出Byte的指定一位數據并寫入到SDA線MyI2C_W_SCL(1); //釋放SCL,從機在SCL高電平期間讀取SDAMyI2C_W_SCL(0); //拉低SCL,主機開始發送下一位數據}
}/*** 函 數:I2C接收一個字節* 參 數:無* 返 回 值:接收到的一個字節數據,范圍:0x00~0xFF*/
uint8_t MyI2C_ReceiveByte(void)
{uint8_t i, Byte = 0x00; //定義接收的數據,并賦初值0x00,此處必須賦初值0x00,后面會用到MyI2C_W_SDA(1); //接收前,主機先確保釋放SDA,避免干擾從機的數據發送for (i = 0; i < 8; i ++) //循環8次,主機依次接收數據的每一位{MyI2C_W_SCL(1); //釋放SCL,主機機在SCL高電平期間讀取SDAif (MyI2C_R_SDA()){Byte |= (0x80 >> i);} //讀取SDA數據,并存儲到Byte變量//當SDA為1時,置變量指定位為1,當SDA為0時,不做處理,指定位為默認的初值0MyI2C_W_SCL(0); //拉低SCL,從機在SCL低電平期間寫入SDA}return Byte; //返回接收到的一個字節數據
}/*** 函 數:I2C發送應答位* 參 數:Byte 要發送的應答位,范圍:0~1,0表示應答,1表示非應答* 返 回 值:無*/
void MyI2C_SendAck(uint8_t AckBit)
{MyI2C_W_SDA(AckBit); //主機把應答位數據放到SDA線MyI2C_W_SCL(1); //釋放SCL,從機在SCL高電平期間,讀取應答位MyI2C_W_SCL(0); //拉低SCL,開始下一個時序模塊
}/*** 函 數:I2C接收應答位* 參 數:無* 返 回 值:接收到的應答位,范圍:0~1,0表示應答,1表示非應答*/
uint8_t MyI2C_ReceiveAck(void)
{uint8_t AckBit; //定義應答位變量MyI2C_W_SDA(1); //接收前,主機先確保釋放SDA,避免干擾從機的數據發送MyI2C_W_SCL(1); //釋放SCL,主機機在SCL高電平期間讀取SDAAckBit = MyI2C_R_SDA(); //將應答位存儲到變量里MyI2C_W_SCL(0); //拉低SCL,開始下一個時序模塊return AckBit; //返回定義應答位變量
}
1. 初始化階段
c
運行
MyI2C_Init(); // 初始化I2C引腳
- 硬件準備:將 SCL (PB10) 和 SDA (PB11) 配置為開漏輸出,并默認拉高(釋放總線)
- 關鍵配置:開漏輸出允許線與特性,支持多設備共享總線
2. 起始信號(Start Condition)
c
運行
MyI2C_Start();
-
時序要求:SCL 為高電平時,SDA 由高變低
-
代碼實現
:
c
運行
MyI2C_W_SDA(1); // 確保SDA為高 MyI2C_W_SCL(1); // 確保SCL為高 MyI2C_W_SDA(0); // SDA拉低,產生起始邊沿 MyI2C_W_SCL(0); // SCL拉低,準備發送數據
-
作用:通知總線上所有從機 “通信開始”,并使總線進入占用狀態
3. 尋址階段
c
運行
MyI2C_SendByte(0xD0); // 發送從機地址+寫位(0)
MyI2C_ReceiveAck(); // 接收從機應答
-
地址構成
:7 位從機地址 + 1 位讀寫位(0 = 寫,1 = 讀)
- 例如:MPU6050 默認地址為
0x68
,寫操作時發送0xD0
(0x68<<1 | 0)
- 例如:MPU6050 默認地址為
-
應答機制
:
- 從機接收到地址后,若匹配則拉低 SDA(發送 ACK=0)
- 主機檢測到 ACK 后繼續通信,否則終止
4. 數據傳輸階段
寫操作(主機→從機)
c
運行
MyI2C_SendByte(MPU6050_WHO_AM_I); // 發送寄存器地址
MyI2C_ReceiveAck(); // 等待應答
MyI2C_SendByte(data); // 發送數據
MyI2C_ReceiveAck(); // 等待應答
-
數據發送流程
:
- 主機在 SCL 低電平時將數據位放到 SDA 上
- 拉高 SCL,從機在 SCL 高電平期間讀取數據
- 拉低 SCL,準備發送下一位
- 重復 8 次,完成 1 字節傳輸
-
應答規則:每字節傳輸后,接收方需發送 ACK/NACK
讀操作(從機→主機)
c
運行
MyI2C_ReceiveByte(); // 接收數據
MyI2C_SendAck(1); // 發送非應答(停止接收)
-
數據接收流程
:
- 主機釋放 SDA(拉高)
- 從機在 SCL 低電平時將數據位放到 SDA 上
- 主機在 SCL 高電平期間讀取數據
- 拉低 SCL,準備接收下一位
- 重復 8 次,完成 1 字節傳輸
-
應答控制
:
- 主機若需要繼續接收數據,發送 ACK (0)
- 主機若接收完畢,發送 NACK (1)
5. 停止信號(Stop Condition)
c
運行
MyI2C_Stop();
-
時序要求:SCL 為高電平時,SDA 由低變高
-
代碼實現
:
c
運行
MyI2C_W_SDA(0); // 確保SDA為低 MyI2C_W_SCL(1); // 拉高SCL MyI2C_W_SDA(1); // SDA拉高,產生停止邊沿
-
作用:釋放總線,結束本次通信
6. 完整通信示例(讀 MPU6050 的 WHO_AM_I 寄存器)
c
運行
uint8_t ReadMPU6050ID(void) {uint8_t id;// 寫階段:指定寄存器地址MyI2C_Start(); // 起始信號MyI2C_SendByte(0xD0); // 發送從機寫地址MyI2C_ReceiveAck(); // 等待應答MyI2C_SendByte(MPU6050_WHO_AM_I); // 發送寄存器地址MyI2C_ReceiveAck(); // 等待應答// 讀階段:讀取寄存器值MyI2C_Start(); // 重新發起始信號(Re-Start)MyI2C_SendByte(0xD1); // 發送從機讀地址MyI2C_ReceiveAck(); // 等待應答id = MyI2C_ReceiveByte(); // 讀取數據MyI2C_SendAck(1); // 發送非應答(停止接收)MyI2C_Stop(); // 停止信號return id;
}
7. 關鍵時序細節
-
數據有效性:SDA 線上的數據必須在 SCL 高電平期間保持穩定
-
邊沿觸發:SCL 的上升沿觸發數據采樣,下降沿允許數據變化
-
應答位處理
:
- 發送方發送完 8 位數據后,必須釋放 SDA
- 接收方在第 9 個時鐘周期發送 ACK/NACK
-
時鐘拉伸
(Clock Stretching):
- 從機若處理不及,可拉低 SCL 強制主機等待
MyI2C.h
#ifndef __MYI2C_H
#define __MYI2C_Hvoid MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);#endif
MPU6050.c
#include "stm32f10x.h" // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"#define MPU6050_ADDRESS 0xD0 //MPU6050的I2C從機地址/*** 函 數:MPU6050寫寄存器* 參 數:RegAddress 寄存器地址,范圍:參考MPU6050手冊的寄存器描述* 參 數:Data 要寫入寄存器的數據,范圍:0x00~0xFF* 返 回 值:無*/
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{MyI2C_Start(); //I2C起始MyI2C_SendByte(MPU6050_ADDRESS); //發送從機地址,讀寫位為0,表示即將寫入MyI2C_ReceiveAck(); //接收應答MyI2C_SendByte(RegAddress); //發送寄存器地址MyI2C_ReceiveAck(); //接收應答MyI2C_SendByte(Data); //發送要寫入寄存器的數據MyI2C_ReceiveAck(); //接收應答MyI2C_Stop(); //I2C終止
}/*** 函 數:MPU6050讀寄存器* 參 數:RegAddress 寄存器地址,范圍:參考MPU6050手冊的寄存器描述* 返 回 值:讀取寄存器的數據,范圍:0x00~0xFF*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{uint8_t Data;MyI2C_Start(); //I2C起始MyI2C_SendByte(MPU6050_ADDRESS); //發送從機地址,讀寫位為0,表示即將寫入MyI2C_ReceiveAck(); //接收應答MyI2C_SendByte(RegAddress); //發送寄存器地址MyI2C_ReceiveAck(); //接收應答MyI2C_Start(); //I2C重復起始MyI2C_SendByte(MPU6050_ADDRESS | 0x01); //發送從機地址,讀寫位為1,表示即將讀取MyI2C_ReceiveAck(); //接收應答Data = MyI2C_ReceiveByte(); //接收指定寄存器的數據MyI2C_SendAck(1); //發送應答,給從機非應答,終止從機的數據輸出MyI2C_Stop(); //I2C終止return Data;
}/*** 函 數:MPU6050初始化* 參 數:無* 返 回 值:無*/
void MPU6050_Init(void)
{MyI2C_Init(); //先初始化底層的I2C/*MPU6050寄存器初始化,需要對照MPU6050手冊的寄存器描述配置,此處僅配置了部分重要的寄存器*/MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); //電源管理寄存器1,取消睡眠模式,選擇時鐘源為X軸陀螺儀MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); //電源管理寄存器2,保持默認值0,所有軸均不待機MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); //采樣率分頻寄存器,配置采樣率MPU6050_WriteReg(MPU6050_CONFIG, 0x06); //配置寄存器,配置DLPFMPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); //陀螺儀配置寄存器,選擇滿量程為±2000°/sMPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); //加速度計配置寄存器,選擇滿量程為±16g
}/*** 函 數:MPU6050獲取ID號* 參 數:無* 返 回 值:MPU6050的ID號*/
uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I); //返回WHO_AM_I寄存器的值
}/*** 函 數:MPU6050獲取數據* 參 數:AccX AccY AccZ 加速度計X、Y、Z軸的數據,使用輸出參數的形式返回,范圍:-32768~32767* 參 數:GyroX GyroY GyroZ 陀螺儀X、Y、Z軸的數據,使用輸出參數的形式返回,范圍:-32768~32767* 返 回 值:無*/
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{uint8_t DataH, DataL; //定義數據高8位和低8位的變量DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); //讀取加速度計X軸的高8位數據DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); //讀取加速度計X軸的低8位數據*AccX = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); //讀取加速度計Y軸的高8位數據DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); //讀取加速度計Y軸的低8位數據*AccY = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); //讀取加速度計Z軸的高8位數據DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); //讀取加速度計Z軸的低8位數據*AccZ = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); //讀取陀螺儀X軸的高8位數據DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); //讀取陀螺儀X軸的低8位數據*GyroX = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); //讀取陀螺儀Y軸的高8位數據DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); //讀取陀螺儀Y軸的低8位數據*GyroY = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); //讀取陀螺儀Z軸的高8位數據DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); //讀取陀螺儀Z軸的低8位數據*GyroZ = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回
}
MPU6050.h
#ifndef __MPU6050_H
#define __MPU6050_Hvoid MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);#endif
MPU6050_reg.h
#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H#define MPU6050_SMPLRT_DIV 0x19
#define MPU6050_CONFIG 0x1A
#define MPU6050_GYRO_CONFIG 0x1B
#define MPU6050_ACCEL_CONFIG 0x1C#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_ACCEL_XOUT_L 0x3C
#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_PWR_MGMT_2 0x6C
#define MPU6050_WHO_AM_I 0x75#endif
1. 配置類寄存器
MPU6050_SMPLRT_DIV
(0x19):采樣率分頻寄存器,設置傳感器數據輸出的采樣率,分頻值 = 寄存器值 + 1,影響加速度計、陀螺儀數據更新速度。MPU6050_CONFIG
(0x1A):配置寄存器,控制低通濾波(LPF)參數,決定加速度計、陀螺儀原始數據的濾波頻率,過濾高頻噪聲。MPU6050_GYRO_CONFIG
(0x1B):陀螺儀配置寄存器,設置陀螺儀量程(如 ±250°/s、±500°/s 等)、自檢使能等。MPU6050_ACCEL_CONFIG
(0x1C):加速度計配置寄存器,設置加速度計量程(如 ±2g、±4g 等)、自檢使能等。
2. 數據輸出寄存器
- 加速度計數據(
MPU6050_ACCEL_XOUT_H~ZOUT_L
,0x3B~0x40):存儲 X/Y/Z 軸加速度原始數據(高 8 位 + 低 8 位),需結合量程換算為實際加速度值(如 g 為單位 )。 - 溫度數據(
MPU6050_TEMP_OUT_H~L
,0x41~0x42):存儲內部溫度傳感器原始數據,通過公式換算為攝氏 / 華氏溫度。 - 陀螺儀數據(
MPU6050_GYRO_XOUT_H~ZOUT_L
,0x43~0x48):存儲 X/Y/Z 軸角速度原始數據(高 8 位 + 低 8 位),結合量程換算為實際角速度(如 °/s 為單位 )。
3. 電源與識別寄存器
MPU6050_PWR_MGMT_1
(0x6B):電源管理寄存器 1,控制設備喚醒、睡眠模式,設置時鐘源(如內部 8MHz 振蕩器、外部時鐘 )。MPU6050_PWR_MGMT_2
(0x6C):電源管理寄存器 2,控制加速度計、陀螺儀各軸的待機模式,實現低功耗配置。MPU6050_WHO_AM_I
(0x75):設備 ID 寄存器,讀取固定值(通常 0x68 ),用于檢測設備是否正常連接、通信。
4.3相關API
1. 引腳配置層 API
函數名 | 功能描述 | 參數說明 | 返回值 |
---|---|---|---|
MyI2C_Init() | 初始化 I2C 引腳(PB10/SCL、PB11/SDA)為開漏輸出,并釋放總線 | 無 | 無 |
MyI2C_W_SCL(uint8_t BitValue) | 設置 SCL 引腳電平(0 = 低,1 = 高),控制時鐘線 | BitValue :0 或 1 | 無 |
MyI2C_W_SDA(uint8_t BitValue) | 設置 SDA 引腳電平(0 = 低,1 = 高),控制數據線 | BitValue :0 或 1 | 無 |
MyI2C_R_SDA(void) | 讀取 SDA 引腳當前電平,用于接收從機數據或應答 | 無 | uint8_t :0 或 1 |
2. 協議層 API
函數名 | 功能描述 | 參數說明 | 返回值 |
---|---|---|---|
MyI2C_Start() | 產生 I2C 起始信號(SCL 高電平時,SDA 由高變低),標志通信開始 | 無 | 無 |
MyI2C_Stop() | 產生 I2C 停止信號(SCL 高電平時,SDA 由低變高),釋放總線 | 無 | 無 |
MyI2C_SendByte(uint8_t Byte) | 發送 1 字節數據(高位先傳),逐位輸出到 SDA 線,并等待從機應答 | Byte :待發送的數據(0x00~0xFF) | 無 |
MyI2C_ReceiveByte(void) | 接收 1 字節數據(高位先收),從 SDA 線讀取從機發送的數據 | 無 | uint8_t :接收到的數據 |
MyI2C_SendAck(uint8_t AckBit) | 發送應答位(控制 SDA),告知從機是否繼續發送數據 | AckBit :0 = 應答(繼續),1 = 非應答(停止) | 無 |
MyI2C_ReceiveAck(void) | 接收從機應答位,判斷從機是否成功接收數據 | 無 |
4.4現象
軟件與硬件現象相同
五、硬件I2C讀寫MPU6050
5.1接線圖
5.2代碼
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"uint8_t ID; //定義用于存放ID號的變量
int16_t AX, AY, AZ, GX, GY, GZ; //定義用于存放各個數據的變量int main(void)
{/*模塊初始化*/OLED_Init(); //OLED初始化MPU6050_Init(); //MPU6050初始化/*顯示ID號*/OLED_ShowString(1, 1, "ID:"); //顯示靜態字符串ID = MPU6050_GetID(); //獲取MPU6050的ID號OLED_ShowHexNum(1, 4, ID, 2); //OLED顯示ID號while (1){MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ); //獲取MPU6050的數據OLED_ShowSignedNum(2, 1, AX, 5); //OLED顯示數據OLED_ShowSignedNum(3, 1, AY, 5);OLED_ShowSignedNum(4, 1, AZ, 5);OLED_ShowSignedNum(2, 8, GX, 5);OLED_ShowSignedNum(3, 8, GY, 5);OLED_ShowSignedNum(4, 8, GZ, 5);}
}
MPU6050.c
#include "stm32f10x.h" // Device header
#include "MPU6050_Reg.h"#define MPU6050_ADDRESS 0xD0 //MPU6050的I2C從機地址/*** 函 數:MPU6050等待事件* 參 數:同I2C_CheckEvent* 返 回 值:無*/
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{uint32_t Timeout;Timeout = 10000; //給定超時計數時間while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS) //循環等待指定事件{Timeout --; //等待時,計數值自減if (Timeout == 0) //自減到0后,等待超時{/*超時的錯誤處理代碼,可以添加到此處*/break; //跳出等待,不等了}}
}/*** 函 數:MPU6050寫寄存器* 參 數:RegAddress 寄存器地址,范圍:參考MPU6050手冊的寄存器描述* 參 數:Data 要寫入寄存器的數據,范圍:0x00~0xFF* 返 回 值:無*/
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成起始條件MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter); //硬件I2C發送從機地址,方向為發送MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6I2C_SendData(I2C2, RegAddress); //硬件I2C發送寄存器地址MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING); //等待EV8I2C_SendData(I2C2, Data); //硬件I2C發送數據MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //等待EV8_2I2C_GenerateSTOP(I2C2, ENABLE); //硬件I2C生成終止條件
}/*** 函 數:MPU6050讀寄存器* 參 數:RegAddress 寄存器地址,范圍:參考MPU6050手冊的寄存器描述* 返 回 值:讀取寄存器的數據,范圍:0x00~0xFF*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{uint8_t Data;I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成起始條件MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter); //硬件I2C發送從機地址,方向為發送MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6I2C_SendData(I2C2, RegAddress); //硬件I2C發送寄存器地址MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //等待EV8_2I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成重復起始條件MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver); //硬件I2C發送從機地址,方向為接收MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); //等待EV6I2C_AcknowledgeConfig(I2C2, DISABLE); //在接收最后一個字節之前提前將應答失能I2C_GenerateSTOP(I2C2, ENABLE); //在接收最后一個字節之前提前申請停止條件MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED); //等待EV7Data = I2C_ReceiveData(I2C2); //接收數據寄存器I2C_AcknowledgeConfig(I2C2, ENABLE); //將應答恢復為使能,為了不影響后續可能產生的讀取多字節操作return Data;
}/*** 函 數:MPU6050初始化* 參 數:無* 返 回 值:無*/
void MPU6050_Init(void)
{/*開啟時鐘*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE); //開啟I2C2的時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //開啟GPIOB的時鐘/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure); //將PB10和PB11引腳初始化為復用開漏輸出/*I2C初始化*/I2C_InitTypeDef I2C_InitStructure; //定義結構體變量I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //模式,選擇為I2C模式I2C_InitStructure.I2C_ClockSpeed = 50000; //時鐘速度,選擇為50KHzI2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; //時鐘占空比,選擇Tlow/Thigh = 2I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //應答,選擇使能I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //應答地址,選擇7位,從機模式下才有效I2C_InitStructure.I2C_OwnAddress1 = 0x00; //自身地址,從機模式下才有效I2C_Init(I2C2, &I2C_InitStructure); //將結構體變量交給I2C_Init,配置I2C2/*I2C使能*/I2C_Cmd(I2C2, ENABLE); //使能I2C2,開始運行/*MPU6050寄存器初始化,需要對照MPU6050手冊的寄存器描述配置,此處僅配置了部分重要的寄存器*/MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); //電源管理寄存器1,取消睡眠模式,選擇時鐘源為X軸陀螺儀MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); //電源管理寄存器2,保持默認值0,所有軸均不待機MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); //采樣率分頻寄存器,配置采樣率MPU6050_WriteReg(MPU6050_CONFIG, 0x06); //配置寄存器,配置DLPFMPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); //陀螺儀配置寄存器,選擇滿量程為±2000°/sMPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); //加速度計配置寄存器,選擇滿量程為±16g
}/*** 函 數:MPU6050獲取ID號* 參 數:無* 返 回 值:MPU6050的ID號*/
uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I); //返回WHO_AM_I寄存器的值
}/*** 函 數:MPU6050獲取數據* 參 數:AccX AccY AccZ 加速度計X、Y、Z軸的數據,使用輸出參數的形式返回,范圍:-32768~32767* 參 數:GyroX GyroY GyroZ 陀螺儀X、Y、Z軸的數據,使用輸出參數的形式返回,范圍:-32768~32767* 返 回 值:無*/
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{uint8_t DataH, DataL; //定義數據高8位和低8位的變量DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); //讀取加速度計X軸的高8位數據DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); //讀取加速度計X軸的低8位數據*AccX = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); //讀取加速度計Y軸的高8位數據DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); //讀取加速度計Y軸的低8位數據*AccY = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); //讀取加速度計Z軸的高8位數據DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); //讀取加速度計Z軸的低8位數據*AccZ = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); //讀取陀螺儀X軸的高8位數據DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); //讀取陀螺儀X軸的低8位數據*GyroX = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); //讀取陀螺儀Y軸的高8位數據DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); //讀取陀螺儀Y軸的低8位數據*GyroY = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); //讀取陀螺儀Z軸的高8位數據DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); //讀取陀螺儀Z軸的低8位數據*GyroZ = (DataH << 8) | DataL; //數據拼接,通過輸出參數返回
}
MPU6050.h
#ifndef __MPU6050_H
#define __MPU6050_Hvoid MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);#endif
MPU6050_reg.h
5.3相關API
1. 核心功能 API 列表
函數名 | 功能描述 | 參數說明 | 返回值 |
---|---|---|---|
MPU6050_Init() | 初始化 MPU6050 傳感器(配置 I2C 接口、喚醒芯片、設置量程等) | 無 | 無 |
MPU6050_GetID() | 讀取 MPU6050 的設備 ID(用于驗證設備連接) | 無 | uint8_t :設備 ID(正常為 0x68) |
MPU6050_WriteReg() | 向 MPU6050 指定寄存器寫入數據 | RegAddress :寄存器地址 Data :寫入數據(0x00~0xFF) | 無 |
MPU6050_ReadReg() | 從 MPU6050 指定寄存器讀取數據 | RegAddress :寄存器地址 | uint8_t :讀取的數據 |
MPU6050_GetData() | 獲取加速度計和陀螺儀的原始數據(16 位) | AccX/Y/Z :加速度計數據指針 GyroX/Y/Z :陀螺儀數據指針 | 無 |
MPU6050_WaitEvent() | 等待 I2C 通信事件完成(內部用于硬件 I2C 的狀態檢測) | I2Cx :I2C 外設指針 I2C_EVENT :目標事件 | 無 |
2. 關鍵 API 詳細說明
2.1 MPU6050_Init () - 傳感器初始化
功能流程:
- 開啟 I2C2 和 GPIOB 時鐘,配置 PB10/PB11 為復用開漏輸出(硬件 I2C 專用引腳)
- 初始化 I2C2 參數:
- 模式:I2C 模式
- 速率:50kHz(標準模式)
- 應答:使能 7 位地址應答
- 配置 MPU6050 寄存器:
- 喚醒芯片(
PWR_MGMT_1 = 0x01
) - 設置加速度計量程 ±16g(
ACCEL_CONFIG = 0x18
) - 設置陀螺儀量程 ±2000°/s(
GYRO_CONFIG = 0x18
)
- 喚醒芯片(
調用示例:
c
運行
MPU6050_Init(); // 初始化后即可讀取傳感器數據
2.2 MPU6050_WriteReg () - 寫寄存器
通信流程:
- 生成 I2C 起始信號
- 發送從機寫地址(0xD0),等待 EV6 事件
- 發送寄存器地址,等待 EV8 事件
- 發送數據,等待 EV8_2 事件
- 生成停止信號
參數說明:
RegAddress
:寄存器地址(如MPU6050_PWR_MGMT_1 = 0x6B
)Data
:寫入數據(如0x01
表示喚醒芯片)
代碼示例:
c
運行
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); // 設置采樣率分頻
2.3 MPU6050_ReadReg () - 讀寄存器
通信流程:
- 生成起始信號→發送寫地址→發送寄存器地址(第一次通信)
- 生成重復起始信號→發送讀地址→接收數據(第二次通信)
- 接收前禁用應答,生成停止信號
關鍵邏輯:
c
運行
uint8_t Data = MPU6050_ReadReg(MPU6050_WHO_AM_I); // 讀設備ID
2.4 MPU6050_GetData () - 獲取傳感器數據
數據處理:
-
每個軸數據由 2 字節組成(高 8 位 + 低 8 位),需拼接為 16 位有符號數
-
示例(加速度 X 軸):
c
運行
DataH = MPU6050_ReadReg(0x3B); // 高字節 DataL = MPU6050_ReadReg(0x3C); // 低字節 *AccX = (DataH << 8) | DataL; // 拼接為16位
-
量程與數值轉換:
- 加速度 ±16g:1LSB = 16g / 32768 ≈ 0.000488g
- 陀螺儀 ±2000°/s:1LSB = 2000°/s/ 32768 ≈ 0.061°/s
3. 硬件 I2C 與軟件模擬的差異
特性 | 硬件 I2C(當前代碼) | 軟件模擬 I2C(之前代碼) |
---|---|---|
時序控制 | 由硬件外設自動生成,精度高 | 依賴軟件延時,精度較低 |
CPU 占用率 | 低(通信時 CPU 可處理其他任務) | 高(需全程控制 GPIO 翻轉) |
速率上限 | 支持高速模式(400kHz+) | 通常≤100kHz |
代碼復雜度 | 需配置 I2C 外設寄存器,邏輯較復雜 | 僅操作 GPIO,代碼更簡潔 |
穩定性 | 抗干擾能力強,適合高速通信 | 易受 CPU 負載影響,需額外防抖 |
5.4現象
一、正常運行現象
-
系統初始化階段
- OLED 屏幕第 1 行顯示
ID:68
(MPU6050 默認 ID 為 0x68),若 ID 顯示為其他值(如 0xFF),則說明通信異常。 - 硬件連接正常時,MPU6050 芯片表面輕微發熱(工作電流約 3.6mA)。
- OLED 屏幕第 1 行顯示
-
數據采集與顯示階段
-
加速度計數據(AX/AY/AZ):
- 靜止時,Z 軸數據約為 + 16384(對應 + 1g,因重力方向豎直向下),X/Y 軸接近 0(±50 以內)。
- 傾斜傳感器時,對應軸數據線性變化(如 X 軸傾斜 45°,AX≈16384×sin45°≈11585)。
-
陀螺儀數據(GX/GY/GZ) :
- 靜止時,各軸數據接近 0(±50 以內),旋轉傳感器時,對應軸數據隨角速度變化(如繞 Z 軸順時針旋轉,GZ 為正值)。
-
OLED 顯示格式(示例):
plaintext
ID:68 AX:+1234 GX:-056 AY:-0456 GY:+078 AZ:+16384 GZ:-012
-