前言
在嵌入式系統中,設備間的短距離通信協議中,I2C(Inter-Integrated Circuit,集成電路互連)以其信號線少、布線簡單、支持多從機等特點,被廣泛應用于傳感器、EEPROM、OLED屏等中低速外設的通信場景。與SPI的高速全雙工和UART的異步簡單相比,I2C僅需2根線即可實現多設備間的半雙工通信,在資源受限的嵌入式系統中極具優勢。
本文將從I2C協議基礎出發,系統解析STM32 I2C外設的工作原理、硬件設計要點、軟件配置流程及實戰案例,涵蓋寄存器級編程、HAL庫應用、中斷與DMA傳輸等核心內容,并提供詳細的調試技巧與常見問題解決方案,旨在幫助嵌入式開發者全面掌握STM32中I2C的應用。
一、I2C協議基礎
1.1 什么是I2C?
I2C是一種由飛利浦(現恩智浦)公司開發的同步串行通信協議,主要用于短距離、低速設備間的通信(通常速率≤400kbps,高速模式可達3.4Mbps)。其核心特點包括:
- 雙線通信:僅需SCL(Serial Clock,串行時鐘)和SDA(Serial Data,串行數據)兩根信號線;
- 多從機支持:通過7位或10位地址區分從機,總線上可連接多個從機(理論上最多127個7位地址設備);
- 主從式架構:通信由主機(如STM32)發起,從機(如傳感器)被動響應,主機負責產生時鐘信號;
- 半雙工通信:同一時刻只能發送或接收數據,通過SDA線雙向傳輸。
1.2 I2C信號線組成
I2C通信僅需兩根信號線,所有設備的SCL和SDA線并聯連接:
信號線 | 功能描述 |
---|---|
SCL | 時鐘線,由主機產生,用于同步數據傳輸(高低電平切換的頻率決定通信速率)。 |
SDA | 數據線,雙向傳輸數據,主機和從機通過該線發送或接收數據(需開漏輸出+上拉電阻)。 |
關鍵特性:
- SDA和SCL均需通過上拉電阻(通常4.7kΩ~10kΩ)接電源(如3.3V),確保空閑時為高電平;
- 信號線采用開漏輸出(或集電極開路),支持“線與”邏輯(多個設備同時拉低時,總線為低電平;僅當所有設備釋放時,總線才為高電平)。
1.3 I2C通信速率
I2C定義了三種主要通信速率(不同版本協議略有差異):
- 標準模式(Standard-mode):100kbps(最常用);
- 快速模式(Fast-mode):400kbps;
- 高速模式(Fast-mode Plus):1Mbps(部分設備支持);
- 超高速模式(High-speed mode):3.4Mbps(高端設備支持)。
注意:總線上所有設備的最高支持速率必須≥主機使用的速率,否則需以最低速率通信(如主機支持400kbps,但某從機僅支持100kbps,則總線速率需設為100kbps)。
1.4 I2C核心時序信號
I2C協議通過特定的時序信號定義通信的開始、結束、數據傳輸和應答,是協議理解的核心。
1.4.1 起始位(S)與停止位(P)
- 起始位(S):當SCL為高電平時,SDA從高電平跳變為低電平(下降沿),標志一次通信的開始;
- 停止位(P):當SCL為高電平時,SDA從低電平跳變為高電平(上升沿),標志一次通信的結束。
(示意圖:SCL高電平時,SDA下降沿為起始位,上升沿為停止位)
1.4.2 數據傳輸時序
- 數據以8位為一幀,高位在前(MSB),低位在后(LSB);
- 每傳輸1位數據,SCL需有一個高電平脈沖(數據在SCL高電平時保持穩定,避免信號跳變導致誤讀);
- 8位數據傳輸完成后,緊跟一個應答位(ACK)或非應答位(NACK)。
1.4.3 應答信號(ACK/NACK)
- 應答位(ACK):接收方在第9個SCL時鐘周期內將SDA拉低,表示成功接收數據;
- 非應答位(NACK):接收方在第9個SCL時鐘周期內讓SDA保持高電平,表示未接收數據(如數據錯誤、從機忙等)。
規則:
- 主機發送數據時,由從機產生ACK/NACK;
- 主機接收數據時,由主機產生ACK/NACK(除最后一個字節,通常用NACK表示接收結束)。
1.5 I2C通信幀結構
一次完整的I2C通信由“起始位→地址幀→數據幀→停止位”組成,根據方向(讀/寫)不同,幀結構略有差異。
1.5.1 主機向從機寫數據(寫操作)
幀結構:S → [從機地址+W] → ACK → [數據1] → ACK → [數據2] → ACK → ... → P
S
:起始位;[從機地址+W]
:7位從機地址+1位寫標志(0),共8位;ACK
:從機應答;[數據n]
:主機發送的n字節數據;P
:停止位。
1.5.2 主機從從機讀數據(讀操作)
幀結構:S → [從機地址+R] → ACK → [數據1] → ACK → [數據2] → ACK → ... → [數據n] → NACK → P
[從機地址+R]
:7位從機地址+1位讀標志(1),共8位;- 最后一個數據字節后,主機發送NACK,表示不再接收數據,隨后發送停止位。
1.5.3 復合操作(先寫后讀,如讀指定寄存器)
部分從機(如EEPROM、傳感器)需先寫入寄存器地址,再讀取數據,幀結構為:
S → [從機地址+W] → ACK → [寄存器地址] → ACK → S → [從機地址+R] → ACK → [數據] → NACK → P
- 中間的
S
為“重復起始位”(Repeated Start),用于連續通信而不釋放總線。
1.6 I2C地址機制
I2C通過地址區分總線上的從機,地址長度有兩種:
- 7位地址:最常用,范圍0127(其中0為廣播地址,1127為有效地址);
- 10位地址:擴展地址,支持更多從機(僅部分設備支持)。
地址映射:從機地址由硬件引腳和固定地址組成,例如EEPROM AT24C02的固定地址為0xA0,其A0/A1/A2引腳接高/低電平可配置低3位地址(如A0=0、A1=0、A2=0時,地址為0xA0)。
二、STM32 I2C外設詳解
STM32系列芯片(如F1、F4、H7等)普遍集成多個I2C外設(如F103有2個I2C,F407有3個I2C),支持主機模式、從機模式及多種高級特性。
2.1 I2C外設主要特性
以STM32F103(中低端型號)為例,其I2C外設核心特性如下:
- 支持主機模式和從機模式;
- 支持7位和10位地址;
- 支持標準模式(100kbps)和快速模式(400kbps);
- 支持軟件或硬件應答控制;
- 支持中斷和DMA傳輸(減少CPU占用);
- 支持 SMBus(系統管理總線)協議(兼容I2C);
- 內置仲裁和時鐘同步機制(多主機場景);
- 支持數據校驗和錯誤檢測(如應答錯誤、仲裁丟失)。
高端型號(如F4、H7)的I2C外設性能更強,例如F407支持快速模式Plus(1Mbps),H7系列支持超時檢測和更多錯誤處理機制。
2.2 引腳映射
I2C外設的SCL/SDA引腳通過復用功能配置,不同型號的映射不同。以STM32F103C8T6為例,I2C1的默認引腳為:
- SCL:PB6(復用開漏輸出);
- SDA:PB7(復用開漏輸出)。
若默認引腳被占用,可通過重映射功能切換(如I2C1可重映射到PB8/PB9),配置時需:
- 使能AFIO時鐘(
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN
); - 通過AFIO重映射寄存器(
AFIO->MAPR
)配置映射關系。
2.3 時鐘源與速率計算
I2C的時鐘源來自APB1總線(所有I2C外設均掛載APB1):
- STM32F103的APB1時鐘最高36MHz;
- 速率計算公式:
I2C時鐘頻率 = APB1時鐘頻率 / (16 + 2*CCR*TRISE)
其中:CCR
(時鐘控制寄存器):配置分頻系數,決定SCL高/低電平時間;TRISE
(上升時間寄存器):配置SDA/SCL信號的上升時間(與速率相關)。
示例:標準模式(100kbps)配置(APB1=36MHz):
TRISE = 36 + 1 = 37
(標準模式下,TRISE=APB1時鐘頻率(MHz) + 1);CCR = 36000000 / (2 * 100000 * 2) = 90
(標準模式下,高/低電平時間相等,總周期=1/100000=10μs,故高電平時間=5μs,CCR=5μs/(1/36MHz)=180?此處需根據手冊精確計算,不同模式公式略有差異)。
2.4 核心寄存器解析
STM32 I2C的配置與操作通過以下核心寄存器實現(以F103為例):
2.4.1 控制寄存器1(I2C_CR1)
位段 | 功能描述 |
---|---|
PE[0] | 外設使能:1=使能I2C;0=禁用(配置前需禁用)。 |
ACK[10] | 應答使能:1=使能應答(接收數據后自動產生ACK);0=禁用(產生NACK)。 |
START[8] | 起始位生成:1=產生起始位(自動清0)。 |
STOP[9] | 停止位生成:1=產生停止位(自動清0)。 |
ITEVTEN[14] | 事件中斷使能:1=使能事件中斷(如起始位發送、地址匹配等)。 |
ITBUFEN[15] | 緩沖區中斷使能:1=使能數據緩沖區中斷(如TXE、RXNE)。 |
2.4.2 控制寄存器2(I2C_CR2)
位段 | 功能描述 |
---|---|
FREQ[5:0] | 外設輸入時鐘頻率:配置APB1時鐘頻率(單位MHz,如36MHz則設為0x24)。 |
DMAEN[11] | DMA請求使能:1=使能DMA傳輸。 |
2.4.3 時鐘控制寄存器(I2C_CCR)
位段 | 功能描述 |
---|---|
F/S[15] | 模式選擇:0=標準模式(100kbps);1=快速模式(400kbps)。 |
CCR[11:0] | 時鐘控制:決定SCL線的高/低電平時間(與速率相關)。 |
2.4.4 狀態寄存器1(I2C_SR1)
位段 | 功能描述 |
---|---|
SB[0] | 起始位發送完成:1=起始位已發送(僅主機模式)。 |
ADDR[1] | 地址發送完成:1=地址幀已發送且收到ACK(需結合SR2的ADDR位確認)。 |
TXE[7] | 發送數據寄存器空:1=DR寄存器為空(可寫入下一字節)。 |
RXNE[6] | 接收數據寄存器非空:1=DR寄存器有數據(可讀取)。 |
BTF[2] | 字節傳輸完成:1=數據傳輸完成(最后一字節已發送/接收)。 |
AF[4] | 應答失敗:1=接收方未產生ACK(需軟件清0)。 |
2.4.5 數據寄存器(I2C_DR)
- 8位寄存器,發送時寫入數據,接收時讀取數據;
- 寫入DR會觸發數據發送,讀取DR會清除RXNE標志。
三、I2C硬件設計要點
I2C硬件設計的核心是確保總線信號穩定,避免噪聲干擾和電平不匹配,以下是關鍵設計要點:
3.1 上拉電阻選擇
SDA和SCL線必須通過上拉電阻接電源,電阻值選擇需考慮:
- 推薦值:4.7kΩ~10kΩ(標準模式常用4.7kΩ,快速模式可減小至2.2kΩ);
- 總線上設備數量:設備越多,負載電容越大,需減小電阻值(但不宜過小,避免電流過大);
- 電源電壓:3.3V系統常用4.7kΩ,5V系統可選用10kΩ。
布局建議:上拉電阻盡量靠近I2C主機,縮短信號線到電源的路徑,減少噪聲。
3.2 信號線布局
- 長度限制:標準模式下,信號線長度建議≤1米;快速模式下≤0.5米,過長會導致信號延遲和反射;
- 走線規范:SDA和SCL線應平行走線,長度盡可能一致,避免交叉或靠近高速信號線(如SPI的SCK、電機驅動線);
- 接地處理:信號線下方鋪地平面,增強抗干擾能力;總線上所有設備需共地,避免地電位差。
3.3 電平匹配
若總線上存在不同電平的設備(如3.3V的STM32和5V的EEPROM),需進行電平轉換:
- 方案1:使用專用電平轉換芯片(如PCA9306),支持雙向電平轉換;
- 方案2:利用開漏輸出特性,將3.3V設備的SDA/SCL通過上拉電阻接5V(需確保3.3V設備的GPIO容忍5V輸入)。
3.4 多從機地址沖突處理
當多個從機的默認地址沖突時,可通過硬件引腳修改從機地址:
- 多數從機(如AT24C02、SHT30)提供地址配置引腳(如A0/A1/A2),通過接高/低電平改變地址的低幾位;
- 例如:AT24C02的固定地址為0xA0,A0引腳接GND時地址為0xA0,接VCC時為0xA2。
四、I2C軟件配置步驟
本節以STM32F103為主機,實現與從機的I2C通信,分別介紹寄存器級和HAL庫的配置方法,以“標準模式(100kbps)、7位地址、主機模式”為基礎配置。
4.1 寄存器級配置(I2C1,100kbps)
步驟1:使能時鐘
// 使能GPIOB、I2C1和AFIO時鐘
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN;
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
步驟2:配置GPIO引腳(開漏復用輸出)
// 配置PB6(SCL)和PB7(SDA)為復用開漏輸出
GPIOB->CRL &= ~(GPIO_CRL_MODE6 | GPIO_CRL_CNF6);
GPIOB->CRL |= GPIO_CRL_MODE6_1; // 輸出速率2MHz(低速,I2C無需高速)
GPIOB->CRL |= GPIO_CRL_CNF6_1; // 復用開漏輸出GPIOB->CRL &= ~(GPIO_CRL_MODE7 | GPIO_CRL_CNF7);
GPIOB->CRL |= GPIO_CRL_MODE7_1; // 輸出速率2MHz
GPIOB->CRL |= GPIO_CRL_CNF7_1; // 復用開漏輸出
步驟3:配置I2C1參數(標準模式100kbps)
// 禁用I2C1(配置前必須禁用)
I2C1->CR1 &= ~I2C_CR1_PE;// 配置CR2:APB1時鐘36MHz(0x24=36)
I2C1->CR2 &= ~I2C_CR2_FREQ;
I2C1->CR2 |= 0x24;// 配置CCR:標準模式(100kbps),高/低電平時間相等
I2C1->CCR &= ~I2C_CCR_FS; // 標準模式
I2C1->CCR |= 0x5A; // CCR=90(36MHz/(2*100000*2)=90)// 配置TRISE:標準模式下TRISE=36+1=37
I2C1->TRISE = 0x25;// 使能I2C1,使能應答
I2C1->CR1 |= I2C_CR1_ACK | I2C_CR1_PE;
步驟4:實現基本讀寫函數
// 等待I2C事件(用于判斷通信狀態)
void I2C1_WaitEvent(I2C_TypeDef* I2Cx, uint32_t event) {uint32_t timeout = 0xFFFF;while ((I2Cx->SR1 & event) == 0) {if (timeout-- == 0) return; // 超時退出}
}// 發送起始位
void I2C1_Start(void) {I2C1->CR1 |= I2C_CR1_START; // 產生起始位I2C1_WaitEvent(I2C1, I2C_SR1_SB); // 等待起始位發送完成
}// 發送停止位
void I2C1_Stop(void) {I2C1->CR1 |= I2C_CR1_STOP; // 產生停止位
}// 發送從機地址(7位地址+讀寫標志)
void I2C1_SendAddr(uint8_t addr, uint8_t rw) {addr = (addr << 1) | (rw & 0x01); // 地址左移1位,最低位為讀寫標志(0=寫,1=讀)I2C1->DR = addr;I2C1_WaitEvent(I2C1, I2C_SR1_ADDR); // 等待地址發送完成(void)I2C1->SR1; (void)I2C1->SR2; // 清除ADDR標志(讀SR1和SR2)
}// 向從機發送1字節數據
void I2C1_SendByte(uint8_t data) {I2C1_WaitEvent(I2C1, I2C_SR1_TXE); // 等待發送緩沖區空I2C1->DR = data;I2C1_WaitEvent(I2C1, I2C_SR1_BTF); // 等待字節傳輸完成
}// 從從機接收1字節數據(最后一字節用NACK)
uint8_t I2C1_ReceiveByte(uint8_t is_last) {if (is_last) {I2C1->CR1 &= ~I2C_CR1_ACK; // 最后一字節,禁用應答(NACK)}I2C1_WaitEvent(I2C1, I2C_SR1_RXNE); // 等待接收數據return I2C1->DR;
}// 主機向從機寫數據(addr:7位地址,data:數據,len:長度)
void I2C1_Write(uint8_t addr, uint8_t *data, uint16_t len) {I2C1_Start(); // 起始位I2C1_SendAddr(addr, 0); // 發送寫地址for (uint16_t i = 0; i < len; i++) {I2C1_SendByte(data[i]); // 發送數據}I2C1_Stop(); // 停止位I2C1->CR1 |= I2C_CR1_ACK; // 恢復應答使能
}// 主機從從機讀數據(addr:7位地址,data:接收緩沖區,len:長度)
void I2C1_Read(uint8_t addr, uint8_t *data, uint16_t len) {I2C1_Start(); // 起始位I2C1_SendAddr(addr, 1); // 發送讀地址for (uint16_t i = 0; i < len; i++) {data[i] = I2C1_ReceiveByte(i == len-1); // 接收數據(最后一字節用NACK)}I2C1_Stop(); // 停止位I2C1->CR1 |= I2C_CR1_ACK; // 恢復應答使能
}
4.2 HAL庫配置(基于STM32CubeMX)
步驟1:創建工程與時鐘配置
- 打開STM32CubeMX,選擇芯片型號(如STM32F103C8T6);
- 配置RCC:選擇HSE時鐘,配置系統時鐘為72MHz(APB1時鐘36MHz)。
步驟2:配置I2C1
- 在“Pinout & Configuration”中,左側選擇“Connectivity”→“I2C1”;
- 模式選擇“I2C”(主機模式);
- 參數配置:
- I2C Speed Mode:Standard Mode(100kbps);
- I2C Clock Speed:100000;
- Addressing Mode:7-bit;
- 確認引腳:I2C1_SCL=PB6,I2C1_SDA=PB7(默認引腳)。
步驟3:生成代碼
- 配置工程路徑和IDE(如Keil MDK);
- 生成代碼(確保I2C初始化函數
MX_I2C1_Init()
被正確生成)。
步驟4:HAL庫讀寫函數實現
// 主機向從機寫數據(阻塞式)
HAL_StatusTypeDef I2C1_Write(uint8_t addr, uint8_t *data, uint16_t len) {// 等待總線空閑while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);// 發送數據(7位地址,無寄存器地址)return HAL_I2C_Master_Transmit(&hi2c1, (addr << 1) | 0, data, len, 100);
}// 主機從從機讀數據(阻塞式)
HAL_StatusTypeDef I2C1_Read(uint8_t addr, uint8_t *data, uint16_t len) {while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);// 接收數據(7位地址)return HAL_I2C_Master_Receive(&hi2c1, (addr << 1) | 1, data, len, 100);
}// 先寫寄存器地址再讀數據(如讀傳感器指定寄存器)
HAL_StatusTypeDef I2C1_WriteRead(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len) {while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY);// 發送寄存器地址,再讀取數據(重復起始位)return HAL_I2C_Mem_Read(&hi2c1, (addr << 1) | 0, reg, I2C_MEMADD_SIZE_8BIT, data, len, 100);
}
五、實戰案例:I2C外設通信
5.1 案例1:與AT24C02 EEPROM通信
AT24C02是一款2KB的I2C EEPROM,常用于存儲掉電不丟失的數據(如設備參數、校準值),地址可通過A0/A1/A2引腳配置(默認0xA0)。
5.1.1 關鍵操作
-
寫入數據(頁寫入):
- AT24C02的頁大小為8字節,單次寫入不能超過一頁;
- 需先發送“設備地址+寫”→“存儲地址”→“數據”。
// 向AT24C02指定地址寫入數據(寄存器級) void AT24C02_Write(uint8_t addr, uint8_t *data, uint16_t len) {uint16_t pos = 0;uint8_t page_remain;while (len > 0) {// 計算當前頁剩余空間(頁大小8字節)page_remain = 8 - (addr % 8);if (len < page_remain) page_remain = len;I2C1_Start();I2C1_SendAddr(0xA0 >> 1, 0); // 從機地址0xA0(7位為0x50)I2C1_SendByte(addr); // 存儲地址for (uint8_t i = 0; i < page_remain; i++) {I2C1_SendByte(data[pos++]);}I2C1_Stop();HAL_Delay(5); // 等待EEPROM內部寫入完成(典型5ms)addr += page_remain;len -= page_remain;} }
-
讀取數據:
- 發送“設備地址+寫”→“存儲地址”→“重復起始位”→“設備地址+讀”→“數據”。
// 從AT24C02指定地址讀取數據(HAL庫) void AT24C02_Read(uint8_t addr, uint8_t *data, uint16_t len) {HAL_I2C_Mem_Read(&hi2c1, 0xA0, addr, I2C_MEMADD_SIZE_8BIT, data, len, 100);HAL_Delay(1); }
5.2 案例2:與SHT30溫濕度傳感器通信
SHT30是一款高精度I2C溫濕度傳感器,地址為0x44(A引腳接GND)或0x45(A引腳接VCC),支持測量溫度(-40125℃)和濕度(0100%RH)。
5.2.1 通信流程
-
初始化傳感器:發送軟復位命令(0x30A2),確保傳感器處于就緒狀態。
void SHT30_Reset(void) {uint8_t cmd[2] = {0x30, 0xA2};I2C1_Write(0x44, cmd, 2); // 0x44為7位地址,寫操作HAL_Delay(10); }
-
觸發測量:發送測量命令(0x2400為周期性測量,0x2C06為單次高精度測量)。
-
讀取測量結果:傳感器返回6字節數據(溫度高8位、溫度低8位、溫度CRC、濕度高8位、濕度低8位、濕度CRC)。
// 讀取溫濕度數據(HAL庫) HAL_StatusTypeDef SHT30_Read(float *temp, float *humi) {uint8_t cmd[2] = {0x2C, 0x06}; // 單次高精度測量命令uint8_t data[6];// 發送測量命令if (HAL_I2C_Master_Transmit(&hi2c1, 0x44 << 1, cmd, 2, 100) != HAL_OK) {return HAL_ERROR;}HAL_Delay(50); // 等待測量完成// 讀取6字節數據if (HAL_I2C_Master_Receive(&hi2c1, 0x44 << 1 | 1, data, 6, 100) != HAL_OK) {return HAL_ERROR;}// 校驗CRC(簡化版,實際應計算CRC)if (data[2] != SHT30_CRC8(data, 2) || data[5] != SHT30_CRC8(data+3, 2)) {return HAL_ERROR;}// 轉換溫度(公式參考SHT30數據手冊)uint16_t temp_raw = (data[0] << 8) | data[1];*temp = (temp_raw * 175.0f / 65535.0f) - 45.0f;// 轉換濕度uint16_t humi_raw = (data[3] << 8) | data[4];*humi = humi_raw * 100.0f / 65535.0f;return HAL_OK; }
5.3 案例3:I2C中斷與DMA傳輸(批量數據優化)
對于需要頻繁讀寫大量數據的場景(如從多個傳感器輪詢數據),使用中斷或DMA可減少CPU阻塞時間。
5.3.1 中斷接收配置(HAL庫)
uint8_t rx_buf[16]; // 接收緩沖區// 初始化中斷接收
void I2C1_IT_Init(void) {HAL_I2C_Receive_IT(&hi2c1, (0x44 << 1) | 1, rx_buf, 6); // 從SHT30接收6字節
}// I2C中斷接收完成回調
void HAL_I2C_RxCpltCallback(I2C_HandleTypeDef *hi2c) {if (hi2c == &hi2c1) {// 處理接收數據(如解析溫濕度)rx_complete_flag = 1;// 重新開啟中斷接收HAL_I2C_Receive_IT(&hi2c1, (0x44 << 1) | 1, rx_buf, 6);}
}
5.3.2 DMA發送示例(寫入多個傳感器配置)
uint8_t tx_buf[32]; // 存儲多個傳感器的配置命令// 使用DMA發送配置數據
void I2C1_DMA_Send(uint8_t addr, uint16_t len) {HAL_I2C_Master_Transmit_DMA(&hi2c1, (addr << 1) | 0, tx_buf, len);
}// DMA發送完成回調
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c) {if (hi2c == &hi2c1) {// 發送完成處理tx_complete_flag = 1;}
}
六、I2C高級特性與優化
6.1 從機模式配置
STM32的I2C外設可配置為從機模式,接收其他主機的讀寫操作,適用于多主機系統或作為從設備被控制。
從機模式配置要點(寄存器級):
- 配置從機地址:
I2C_OAR1 = (addr << 1) | I2C_OAR1_ADDMODE;
(7位地址); - 使能地址匹配中斷:
I2C_CR1 |= I2C_CR1_ITEVTEN;
; - 在中斷服務函數中處理地址匹配、數據收發事件。
// I2C1從機中斷服務函數
void I2C1_IRQHandler(void) {if (I2C1->SR1 & I2C_SR1_ADDR) { // 地址匹配(void)I2C1->SR1; (void)I2C1->SR2; // 清除標志if (I2C1->SR2 & I2C_SR2_TRA) { // 主機寫,從機接收// 準備接收數據} else { // 主機讀,從機發送// 準備發送數據}} else if (I2C1->SR1 & I2C_SR1_RXNE) { // 接收數據rx_data = I2C1->DR;} else if (I2C1->SR1 & I2C_SR1_TXE) { // 發送數據I2C1->DR = tx_data;}
}
6.2 總線錯誤處理
I2C通信中常見錯誤(如應答失敗、仲裁丟失)需及時處理,避免總線鎖定:
// 檢測并清除I2C錯誤
void I2C1_ClearError(void) {if (I2C1->SR1 & (I2C_SR1_AF | I2C_SR1_ARLO | I2C_SR1_BERR)) {I2C1->SR1 &= ~(I2C_SR1_AF | I2C_SR1_ARLO | I2C_SR1_BERR); // 清除錯誤標志I2C1->CR1 &= ~I2C_CR1_PE; // 禁用外設I2C1->CR1 |= I2C_CR1_PE; // 重新使能}
}
6.3 低功耗模式下的I2C
在STM32低功耗模式(如STOP模式)下,可通過I2C喚醒芯片:
- 配置I2C為從機模式,使能地址匹配喚醒;
- 進入STOP模式前,確保I2C外設處于使能狀態;
- 當主機發送匹配地址時,I2C產生中斷,喚醒芯片。
七、常見問題與調試技巧
7.1 通信失敗的核心原因
7.1.1 總線無響應(從機無ACK)
- 現象:發送地址后始終無ACK,程序卡在等待ACK的循環中;
- 原因:
- 從機地址錯誤(未考慮讀寫標志位,或硬件引腳配置錯誤);
- 上拉電阻缺失或阻值過大,SDA/SCL線空閑時不是高電平;
- 從機未上電、接線錯誤(如SDA與SCL接反);
- 從機忙(如EEPROM正在內部寫入,需等待)。
- 排查:
- 用萬用表測量SDA/SCL線空閑電平,確認是否為高電平;
- 用示波器觀察地址幀是否正確發送(如地址0xA0的寫幀應為0xA0);
- 降低通信速率(如從100kbps降至10kbps),排除速率不匹配問題。
7.1.2 數據亂碼或校驗錯誤
- 現象:能收到ACK,但數據錯誤(如溫濕度值異常);
- 原因:
- 時序錯誤(如快速模式下未正確配置TRISE/CCR);
- 信號線噪聲過大(靠近干擾源、未鋪地平面);
- 從機未正確初始化(如傳感器未復位)。
- 排查:
- 用邏輯分析儀抓取SDA/SCL波形,對比從機數據手冊的時序要求;
- 檢查從機初始化流程(如發送復位命令、等待就緒)。
7.1.3 總線鎖定(I2C無響應)
- 現象:一次通信失敗后,后續所有I2C操作均無反應;
- 原因:通信中斷(如突然斷電、程序復位)導致SDA/SCL線被拉低,總線處于鎖定狀態;
- 解決:
- 軟件復位I2C外設(禁用后重新使能);
- 若軟件復位無效,可通過GPIO模擬SCL線產生多個時鐘脈沖,釋放總線。
7.2 調試工具與方法
-
邏輯分析儀:
- 推薦使用帶I2C解碼功能的邏輯分析儀(如Saleae),直接解析SDA/SCL線上的地址、數據和應答信號;
- 重點觀察:起始位/停止位是否正確、地址幀是否匹配、ACK是否存在、數據時序是否符合模式要求。
-
最小系統驗證:
- 用“雙線連接”驗證:僅連接STM32與一個從機(如AT24C02),排除其他設備干擾;
- 編寫簡單測試函數(如讀取從機ID),確認基本通信正常后再擴展功能。
-
軟件調試技巧:
- 在關鍵步驟添加日志輸出(通過UART),記錄I2C狀態(如“發送地址0xA0”“收到ACK”);
- 使用HAL庫的
HAL_I2C_GetError()
函數獲取錯誤碼(如HAL_I2C_ERROR_AF
為應答失敗)。
7.3 通信可靠性優化
- 添加重試機制:對偶爾失敗的操作(如傳感器忙),重試2~3次;
- 超時控制:所有I2C操作必須設置超時(如100ms),避免程序卡死;
- CRC校驗:對關鍵數據(如校準參數),在應用層添加CRC校驗,彌補I2C無硬件校驗的不足;
- 避免頻繁啟停:連續讀寫時使用重復起始位(而非多次啟停),減少總線開銷。
八、總結與擴展
I2C協議以其簡潔的硬件設計和靈活的多從機支持,在嵌入式系統中占據重要地位。本文從協議基礎到實戰案例,系統講解了I2C的工作原理、STM32配置方法及調試技巧,核心要點包括:
- I2C通過SCL和SDA雙線通信,依賴起始位、地址幀、應答信號實現數據傳輸;
- STM32 I2C外設支持主/從模式、中斷/DMA傳輸,配置需關注時鐘源、速率參數及時序;
- 硬件設計需重視上拉電阻、信號線布局和電平匹配,直接影響通信穩定性;
- 實戰中需根據從機特性(如EEPROM的頁寫入、傳感器的命令序列)設計通信流程。
未來學習可擴展至:
- 多主機I2C系統的仲裁機制;
- I2C與其他協議(如SPI、UART)的混合通信設計;
- 基于I2C的傳感器網絡(如多個溫濕度傳感器組網);
- 低功耗場景下的I2C休眠與喚醒策略。
掌握I2C不僅是嵌入式開發的基礎技能,更是理解同步通信協議設計的關鍵。通過結合硬件調試工具(如邏輯分析儀)和軟件優化技巧,可有效解決實際開發中的通信問題,提升系統可靠性。
附錄:常用代碼片段
- I2C總線釋放函數(解決總線鎖定):
void I2C1_ReleaseBus(void) {// 配置SDA/SCL為推挽輸出GPIOB->CRL &= ~(GPIO_CRL_CNF6 | GPIO_CRL_CNF7);GPIOB->CRL |= GPIO_CRL_CNF6_0 | GPIO_CRL_CNF7_0;// 產生10個SCL時鐘脈沖,釋放總線for (uint8_t i = 0; i < 10; i++) {GPIOB->BSRR = GPIO_BSRR_BS6; // SCL高HAL_Delay(1);GPIOB->BSRR = GPIO_BSRR_BR6; // SCL低HAL_Delay(1);}// 恢復為復用開漏輸出GPIOB->CRL &= ~(GPIO_CRL_CNF6 | GPIO_CRL_CNF7);GPIOB->CRL |= GPIO_CRL_CNF6_1 | GPIO_CRL_CNF7_1;
}
- CRC8校驗函數(用于SHT30、AT24C02等):
uint8_t I2C_CRC8(uint8_t *data, uint8_t len) {uint8_t crc = 0xFF;for (uint8_t i = 0; i < len; i++) {crc ^= data[i];for (uint8_t j = 0; j < 8; j++) {if (crc & 0x80) {crc = (crc << 1) ^ 0x31;} else {crc <<= 1;}}}return crc;
}
- 多從機地址掃描函數:
// 掃描總線上所有響應的從機地址
void I2C1_ScanSlaves(void) {uint8_t addr;for (addr = 0; addr < 128; addr++) {I2C1_Start();I2C1->DR = (addr << 1) | 0; // 寫地址HAL_Delay(1);if (!(I2C1->SR1 & I2C_SR1_AF)) { // 無應答失敗,即有從機響應printf("Found slave: 0x%02X\n", addr);}I2C1_Stop();HAL_Delay(10);}
}