STM32標準庫-I2C通信

文章目錄

  • 一、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框圖

在這里插入圖片描述

  1. 信號線路
    • SDA(Serial Data Line,串行數據線):雙向數據線,負責在設備間傳輸數據,比如傳感器向主控芯片發送采集的溫濕度信息,或主控芯片向存儲設備寫入指令 。
    • SCL(Serial Clock Line,串行時鐘線):由主設備產生時鐘信號,為數據傳輸提供同步時序,像 I2C 總線通信時,主設備通過 SCL 控制數據在 SDA 上的收發節奏 。
    • SMBALERT(System Management Bus Alert,系統管理總線報警線 ):可用于設備觸發報警、通知等功能,例如監測芯片檢測到異常電壓時,通過該線向主控發送報警信號 。
  2. 核心功能模塊
    • 數據控制模塊:協調 SDA 線上的數據收發,決定何時發送、接收數據,以及處理數據傳輸中的邏輯,比如判斷數據幀的起始、停止等狀態 。
    • 時鐘控制模塊:依據 SCL 線輸入的時鐘信號,管理總線通信時序,同時結合時鐘控制寄存器(CCR)配置,調整時鐘頻率等參數,適配不同設備通信需求 。
    • 數據移位寄存器:在數據傳輸時,實現數據的串行 - 并行轉換。發送數據時,將并行數據逐位串行輸出到 SDA;接收數據時,把 SDA 傳來的串行數據轉為并行,存入數據寄存器(DATA REGISTER ) 。
    • 比較器:用于地址匹配,將總線上接收到的從設備地址,與自身地址寄存器、雙地址寄存器中存儲的地址對比,判斷是否為目標設備,若匹配則響應通信 。
    • 幀錯誤校驗(PEC,Packet Error Checking )計算單元:對傳輸的數據幀進行校驗計算,生成校驗碼,也能對比接收到的 PEC 寄存器內校驗碼,檢測數據傳輸是否出錯,保障數據完整性 。
  3. 寄存器組
    • 自身地址寄存器、雙地址寄存器:存儲 I2C 設備自身的地址信息,支持單地址或雙地址模式,方便主設備尋址,比如一個從設備可設置兩個不同地址,供主設備靈活訪問 。
    • 幀錯誤校驗(PEC)寄存器:存放用于錯誤校驗的相關數據,配合 PEC 計算單元,完成數據幀的校驗工作 。
    • 時鐘控制寄存器(CCR):配置時鐘相關參數,像設置時鐘分頻系數,調節 SCL 線的時鐘頻率,滿足不同通信速率要求,比如高速模式、標準模式切換 。
    • 控制寄存器(CR1&CR2):控制 I2C 總線的工作模式、使能中斷等功能,例如通過 CR1 開啟 I2C 模塊、使能特定中斷,CR2 配置地址模式等 。
    • 狀態寄存器(SR1&SR2 ):實時反饋 I2C 總線的工作狀態,如是否檢測到起始信號、數據是否發送完成、有無錯誤發生等,為主控判斷通信進程提供依據 。
    • 控制邏輯電路:作為 I2C 總線的 “指揮中心”,整合各模塊工作,根據寄存器配置和總線狀態,調度數據傳輸、時鐘管理、地址識別等操作,還能觸發中斷(如數據收發完成中斷、錯誤中斷 ),向 DMA(直接內存訪問 )發出請求并響應,讓數據高效傳輸,減輕 CPU 負擔,廣泛應用于嵌入式系統中芯片間低速數據交互,像單片機與傳感器、EEPROM 等設備的通信 。

3.3I2C基本結構

在這里插入圖片描述

  1. 模塊分工
    時鐘控制器:生成或同步 SCL(時鐘線)信號,就像 “節拍器”,規定數據收發的節奏,讓通信雙方按同一節奏干活 。
    數據控制器:管數據收發邏輯,決定啥時候發數據、咋接收數據,是數據通信的 “指揮官” 。
    移位寄存器:數據 “轉換器”—— 發送時,把 數據寄存器(DR) 里的并行數據,拆成串行一位一位發;接收時,把 SDA 收的串行數據,重新拼成并行存到 DR,適配總線串行傳輸的規則 。
    數據寄存器(DR):臨時存數據的 “小倉庫”,發數據時從這取,收數據時存這,方便 CPU 或其他模塊讀寫 。
    GPIO(通用輸入輸出):芯片對外的 “手”,把內部時鐘(SCL)、數據(SDA)信號送出去,也能從外部收信號,實現芯片和外部設備(比如傳感器、屏幕 )的連接 。
    開關控制:像 “總閘”,可能用來打開 / 關閉 I2C 功能、切換工作模式(比如主設備 / 從設備模式 ),靈活控制總線通斷 。

2.通信流程

比如單片機要給傳感器發指令、收數據:

  • 發數據:CPU 把指令放進 DR移位寄存器 把并行指令拆成串行 → 數據控制器 配合 時鐘控制器 的節奏,通過 GPIOSDA 把指令發出去,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)
  • 應答機制

    • 從機接收到地址后,若匹配則拉低 SDA(發送 ACK=0)
    • 主機檢測到 ACK 后繼續通信,否則終止

4. 數據傳輸階段

寫操作(主機→從機)

c

運行

MyI2C_SendByte(MPU6050_WHO_AM_I);  // 發送寄存器地址
MyI2C_ReceiveAck();                // 等待應答
MyI2C_SendByte(data);              // 發送數據
MyI2C_ReceiveAck();                // 等待應答
  • 數據發送流程

    1. 主機在 SCL 低電平時將數據位放到 SDA 上
    2. 拉高 SCL,從機在 SCL 高電平期間讀取數據
    3. 拉低 SCL,準備發送下一位
    4. 重復 8 次,完成 1 字節傳輸
  • 應答規則:每字節傳輸后,接收方需發送 ACK/NACK

讀操作(從機→主機)

c

運行

MyI2C_ReceiveByte();  // 接收數據
MyI2C_SendAck(1);     // 發送非應答(停止接收)
  • 數據接收流程

    1. 主機釋放 SDA(拉高)
    2. 從機在 SCL 低電平時將數據位放到 SDA 上
    3. 主機在 SCL 高電平期間讀取數據
    4. 拉低 SCL,準備接收下一位
    5. 重復 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. 關鍵時序細節

  1. 數據有效性:SDA 線上的數據必須在 SCL 高電平期間保持穩定

  2. 邊沿觸發:SCL 的上升沿觸發數據采樣,下降沿允許數據變化

  3. 應答位處理

    • 發送方發送完 8 位數據后,必須釋放 SDA
    • 接收方在第 9 個時鐘周期發送 ACK/NACK
  4. 時鐘拉伸

    (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 () - 傳感器初始化

功能流程

  1. 開啟 I2C2 和 GPIOB 時鐘,配置 PB10/PB11 為復用開漏輸出(硬件 I2C 專用引腳)
  2. 初始化 I2C2 參數:
    • 模式:I2C 模式
    • 速率:50kHz(標準模式)
    • 應答:使能 7 位地址應答
  3. 配置 MPU6050 寄存器:
    • 喚醒芯片(PWR_MGMT_1 = 0x01
    • 設置加速度計量程 ±16g(ACCEL_CONFIG = 0x18
    • 設置陀螺儀量程 ±2000°/s(GYRO_CONFIG = 0x18

調用示例

c

運行

MPU6050_Init();  // 初始化后即可讀取傳感器數據
2.2 MPU6050_WriteReg () - 寫寄存器

通信流程

  1. 生成 I2C 起始信號
  2. 發送從機寫地址(0xD0),等待 EV6 事件
  3. 發送寄存器地址,等待 EV8 事件
  4. 發送數據,等待 EV8_2 事件
  5. 生成停止信號

參數說明

  • RegAddress:寄存器地址(如MPU6050_PWR_MGMT_1 = 0x6B
  • Data:寫入數據(如0x01表示喚醒芯片)

代碼示例

c

運行

MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);  // 設置采樣率分頻
2.3 MPU6050_ReadReg () - 讀寄存器

通信流程

  1. 生成起始信號→發送寫地址→發送寄存器地址(第一次通信)
  2. 生成重復起始信號→發送讀地址→接收數據(第二次通信)
  3. 接收前禁用應答,生成停止信號

關鍵邏輯

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現象

一、正常運行現象
  1. 系統初始化階段

    • OLED 屏幕第 1 行顯示 ID:68(MPU6050 默認 ID 為 0x68),若 ID 顯示為其他值(如 0xFF),則說明通信異常。
    • 硬件連接正常時,MPU6050 芯片表面輕微發熱(工作電流約 3.6mA)。
  2. 數據采集與顯示階段

    • 加速度計數據(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
      

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/85084.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/85084.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/85084.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

使用 Azure LLM Functions 與 Elasticsearch 構建更智能的查詢體驗

作者&#xff1a;來自 Elastic Jonathan Simon 及 James Williams 試用這個示例房地產搜索應用&#xff0c;它結合了 Azure Gen AI LLM Functions 與 Elasticsearch&#xff0c;提供靈活的混合搜索結果。在 GitHub Codespaces 中查看逐步配置和運行該示例應用的方法。 更多閱讀…

模糊查詢 的深度技術解析

以下是 模糊查詢 的深度技術解析&#xff0c;涵蓋核心語法、通配符策略、性能優化及實戰陷阱&#xff1a; &#x1f50d; 一、核心運算符&#xff1a;LIKE SELECT * FROM 表名 WHERE 列名 LIKE 模式字符串;&#x1f3af; 二、通配符詳解 通配符作用示例匹配案例%任意長度字符…

[論文閱讀] (39)EuroSP25 CTINEXUS:基于大模型的威脅情報知識圖譜自動構建

《娜璋帶你讀論文》系列主要是督促自己閱讀優秀論文及聽取學術講座&#xff0c;并分享給大家&#xff0c;希望您喜歡。由于作者的英文水平和學術能力不高&#xff0c;需要不斷提升&#xff0c;所以還請大家批評指正&#xff0c;非常歡迎大家給我留言評論&#xff0c;學術路上期…

強化學習三大分類

核心目標&#xff1a; 教會一個智能體&#xff08;比如機器人、游戲AI、推薦系統&#xff09;通過試錯和獎勵&#xff0c;學會在某個環境中完成特定任務的最佳策略。 核心角色&#xff1a; 智能體 (Agent)&#xff1a; 學習者&#xff0c;比如玩游戲的小人、控制溫度的空調系…

城市排水生命線安全運行監測項目

近年來&#xff0c;城市內澇、污水溢流等問題頻發&#xff0c;讓排水管網這一"城市生命線"的安全運行備受關注。如何讓地下的"毛細血管"更智能、更可靠&#xff1f;本文將帶您深入解析城市排水生命線安全運行監測項目的建設邏輯與技術內核&#xff0c;看科…

LeetCode - 34. 在排序數組中查找元素的第一個和最后一個位置

題目 34. 在排序數組中查找元素的第一個和最后一個位置 - 力扣&#xff08;LeetCode&#xff09; 思路 查找左邊界 初始化 left 0, right nums.size() - 1 當 left < right 時循環&#xff1a; 計算中點 mid left (right - left) / 2 如果 nums[mid] < target…

Tesollo四指靈巧手DG-4F:18自由度與多種抓取模式結合實現高精度操作

Tesollo四指靈巧手 DG-4F 是一款具備 18 自由度的多模態末端執行器&#xff0c;采用模塊化結構設計&#xff0c;融合人手靈活性與夾爪高效性特點。該產品兼容 Universal Robots、Techman、Doosan Robotics、Rainbow Robotics 等主流機器人平臺&#xff0c;適用于工業自動化、科…

深入淺出JavaScript 原型鏈:對象繼承的“隱形鏈條”

深入淺出JavaScript 原型鏈&#xff1a;對象繼承的“隱形鏈條” 在 JavaScript 的世界里&#xff0c;原型鏈&#xff08;Prototype Chain&#xff09;是一個核心概念。它如同一條隱形的鏈條&#xff0c;連接著所有對象&#xff0c;使得代碼能夠高效地共享屬性和方法。理解原型…

LINUX中MYSQL的使用

LINUX中MYSQL的使用 MYSQL的數據類型 bool&#xff1a; 布爾類型 0 或者 1 CHAR&#xff1a; 單字符的字符 CHAR&#xff08;n&#xff09;:多字節字符 VARCHAR&#xff08;n&#xff09;&#xff1a;可變長度的字符型 TINYINT &#xff1a; 單字節整型 SMALLINT&#x…

打卡第48天:隨機函數與廣播機制

知識點回顧&#xff1a; 隨機張量的生成&#xff1a;torch.randn函數卷積和池化的計算公式&#xff08;可以不掌握&#xff0c;會自動計算的&#xff09;pytorch的廣播機制&#xff1a;加法和乘法的廣播機制 ps&#xff1a;numpy運算也有類似的廣播機制&#xff0c;基本一致 …

學習昇騰開發的第四天--基本指令

1、查看npu當前狀態信息 npu-smi info 2、查看NPU的ID npu-smi info -l3、調用python python3 4、修改用戶名 su - HwHiAiUser 5、查看cann版本 cat /usr/local/Ascend/ascend-toolkit/latest/compiler/version.info 6、刪除文件夾 sudo rm -rf HelloWorld7、在本地環…

vue3 - 自定義hook

自定義hook 簡單點來說就是將人物或者訂單的所有數據和方法放在一個ts文件里面 這樣便于維護 假如一個人只需要管 人物的模塊 那他只需要操作usePerson.ts文件就可以了 //useDog.ts import { ref,reactive} from vue; import axios from axios;export default function(){…

【python】bash: !‘: event not found

報錯 # 2. 測試smplx是否工作&#xff08;可能不需要chumpy&#xff09; python -c "import smplx; print(? smplx works!)"bash: !: event not found 分析 這是bash的歷史擴展問題&#xff0c;感嘆號被解釋為歷史命令。用這些方法解決&#xff1a; &#x1f680…

【Python打卡Day47】注意力熱力圖可視化@浙大疏錦行

可視化空間注意力熱力圖的意義&#xff1a; 提升模型可解釋性 熱力圖能直觀展示模型決策的依據區域&#xff0c;破除深度學習"黑箱"困境。例如在圖像識別中&#xff0c;可以看到模型識別"貓"是因為關注了貓耳和胡須區域&#xff0c;識別"禁止通行&qu…

樹狀數組 2

L - 樹狀數組 2 洛谷 - P3368 Description 如題&#xff0c;已知一個數列&#xff0c;你需要進行下面兩種操作&#xff1a; 將某區間每一個數加上 x&#xff1b; 求出某一個數的值。 Input 第一行包含兩個整數 N、M&#xff0c;分別表示該數列數字的個數和操作的總個數。…

YOLOv2 技術詳解:目標檢測的又一次飛躍

&#x1f9e0; YOLOv2 技術詳解&#xff1a;目標檢測的又一次飛躍 一、前言 在 YOLOv1 提出后&#xff0c;雖然實現了“實時性 單階段”的突破&#xff0c;但其在精度和小物體檢測方面仍有明顯不足。為了彌補這些缺陷&#xff0c;Joseph Redmon 等人在 2017 年提出了 YOLOv2…

JAFAR Jack up Any Feature at Any Resolution

GitHub PaPer JAFAR: Jack up Any Feature at Any Resolution 摘要 基礎視覺編碼器已成為各種密集視覺任務的核心組件。然而&#xff0c;它們的低分辨率空間特征輸出需要特征上采樣以產生下游任務所需的高分辨率模式。在這項工作中&#xff0c;我們介紹了 JAFAR——一種輕量級…

SamWaf 開源輕量級網站防火墻源碼(源碼下載)

SamWaf網站防火墻是一款適用于小公司、工作室和個人網站的開源輕量級網站防火墻&#xff0c;完全私有化部署&#xff0c;數據加密且僅保存本地&#xff0c;一鍵啟動&#xff0c;支持Linux&#xff0c;Windows 64位,Arm64。 主要功能&#xff1a; 代碼完全開源 支持私有化部署…

79Qt窗口_QDockWidget的基本使用

目錄 4.1 浮動窗?的創建 4.2 設置停靠的位置 浮動窗? 在 Qt 中&#xff0c;浮動窗?也稱之為鉚接部件。浮動窗?是通過 QDockWidget類 來實現浮動的功能。浮動窗 ??般是位于核?部件的周圍&#xff0c;可以有多個。 4.1 浮動窗?的創建 浮動窗?的創建是通過 QDockWidget…

UE/Unity/Webgl云渲染推流網址,如何與外部網頁嵌套和交互?

需求分析&#xff1a;用threejs開發的數字孿生模型&#xff0c; 但是通過webgl技術網頁中使用&#xff0c;因為模型數據量大&#xff0c;加載比較慢&#xff0c;且需要和其他的業務系統進行網頁嵌套和交互&#xff0c;使用云渲染技術形成的推流網址&#xff0c;如何與外部網頁嵌…