主流 GPIO 模擬 I2C 多為輪詢延時,佔用大量 CPU 周期。本實現採用定時器中斷推動狀態機,實現非阻塞、後台自動化的 I2C 主機控制器。
IIC_BUSY 標誌同步等待
// 每路 I2C 獨立狀態
typedef struct {
// 狀態機階段
uint8_t StartState, StopState;
uint8_t ReadByteState, WriteByteState;
uint8_t ReadState, WriteState; // 高層流程
// 通信參數
uint8_t Command; // 1=Read, 0=Write
uint8_t Device; // 7位設備地址
uint32_t SubAddr; // 寄存器地址 (7/16位)
uint8_t SubAddrLen; // 地址長度
uint8_t *TransferBuf; // 數據緩衝區
uint16_t TransferCount; // 數據長度
// 運行時變量
uint8_t TransferByte; // 當前收發字節
uint8_t bit, dat; // 位操作暫存
uint8_t ReadStop; // 最後一字節標誌
uint8_t WriteACK; // 寫入時收到的 ACK
uint8_t IIC_BUSY; // 忙標誌
uint8_t ERROR; // 錯誤標誌
} IIC_State;
// 回調函數組
typedef struct {
void (*OnTx)(void); // 寫完成
void (*OnRx)(void); // 讀完成
void (*OnErr)(void); // 錯誤
} IIC_Callback;
static uint8_t IIC_StartStateMachine(uint8_t IIC) {
switch(p.StartState) {
case 0: SDA_Set(IIC); SCL_Set(IIC); p.StartState++; break;
case 1: SDA_Clear(IIC); p.StartState++; break; // SDA 下降沿 (SCL 高)
case 2: SCL_Clear(IIC); p.StartState=0; break; // 拉低 SCL 準備數據
}
return p.StartState; // 0 表示完成
}
static uint8_t IIC_WriteByteStateMachine(uint8_t IIC) {
switch(p.WriteByteState) {
case 0: p.dat = p.TransferByte; p.bit = 8; p.WriteByteState++; break;
case 1: if(p.dat & 0x80) SDA_Set(IIC); else SDA_Clear(IIC); p.WriteByteState++; break;
case 2: SCL_Set(IIC); p.WriteByteState++; break; // SCL 高,設備採樣
case 3: p.dat <<= 1; SCL_Clear(IIC); p.bit--; // SCL 低,準備下一位
if(p.bit) p.WriteByteState = 1; else p.WriteByteState++; break;
case 4: SetIicSdaDir(IIC, IN); p.WriteByteState++; break; // 釋放 SDA 讀 ACK
case 5: SCL_Set(IIC); p.WriteByteState++; break;
case 6: p.WriteACK = SDA_Read(IIC); SCL_Clear(IIC); // 讀取 ACK (0=ACK, 1=NAK)
SetIicSdaDir(IIC, OUT); p.WriteByteState++; break;
case 7: p.WriteByteState = 0; break;
}
return p.WriteByteState;
}
static uint8_t IIC_ReadStateMachine(uint8_t IIC) {
switch(p.ReadState) {
case 1: if(IIC_StartStateMachine(IIC)==0) p.ReadState++; break; // START
case 2: p.TransferByte = p.Device; p.ReadState++; break; // 發送設備地址+W
case 3: if(IIC_WriteByteStateMachine(IIC)==0) { // 等待寫完
if(p.WriteACK) { p.ReadState=14; break; } // NAK → STOP+錯誤
if(p.SubAddrLen) p.ReadState++; else p.ReadState+=3; // 有/無寄存器地址
} break;
case 4-5: ... 發送寄存器地址 (高位在前) ...
case 6: if(IIC_StartStateMachine(IIC)==0) p.ReadState++; break; // 重復 START
case 7: p.TransferByte = p.Device | 0x01; p.ReadState++; break; // 發送設備地址+R
case 8: if(IIC_WriteByteStateMachine(IIC)==0) { // 等待寫完
if(p.WriteACK) { p.ReadState=14; break; }
if(p.TransferCount==1) p.ReadState+=3; else p.ReadState++; // 最後一字節特殊處理
} break;
case 9-12: ... 讀取數據字節 (倒數第二字節送 ACK,最後一字節送 NAK) ...
case 13: if(IIC_StopStateMachine(IIC)==0) { p.ReadState=0; p.IIC_BUSY=0; p.ERROR=0; if(q.OnRx) q.OnRx(); } break;
case 14: if(IIC_StopStateMachine(IIC)==0) { p.ReadState=0; p.IIC_BUSY=0; p.ERROR=1; if(q.OnErr) q.OnErr(); } break;
}
return p.ReadState;
}
// 初始化:指定 I2C 編號、定時器周期 (微秒,對應半週期)
void IIC_Init(uint8_t IIC, uint16_t MicroSecond);
// 設置回調
void IIC_SetCallback(uint8_t IIC, void(*OnTx)(void), void(*OnRx)(void), void(*OnErr)(void));
// 非阻塞發起讀寫,返回 1=已啟動, 0=忙碌
uint8_t I2C_Read7(uint8_t IIC, uint8_t device, uint8_t Addr, uint8_t *Buf, uint8_t Count);
uint8_t I2C_Read16(uint8_t IIC, uint8_t device, uint16_t Addr, uint8_t *Buf, uint8_t Count);
uint8_t I2C_WriteByte7(uint8_t IIC, uint8_t device, uint8_t Addr, uint8_t Data);
uint8_t I2C_Write16(uint8_t IIC, uint8_t device, uint16_t Addr, uint8_t *Buf, uint8_t Count);
// 查詢模式同步等待
while(iic_state[IIC].IIC_BUSY); // 或在回調中設置標誌
void TIM5_IRQHandler(void) { // I2C1
if(TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET) {
TIM_ClearITPendingBit(TIM5, TIM_IT_Update);
if(IIC_StateMachine(0) == 0) { // 狀態機返回 0 表示本次中斷處理完畢
if(iic_state[0].IIC_BUSY == 0) TIM_Cmd(TIM5, DISABLE); // 關閉定時器省電
}
}
}
// TIM6_DAC_IRQHandler (I2C2), TIM7_IRQHandler (I2C3) 同理
| 指標 | 輪詢延時法 | 本中斷狀態機法 |
|---|---|---|
| CPU 佔用 | 100% (空循環等待) | ~1-5% (僅中斷處理) |
| 實時性 | 阻塞主循環 | 後台並行,不影響主任務 |
| 時序精度 | 受中斷/調度干擾大 | 硬體定時器基準,抖動極小 |
| 代碼複雜度 | 低 | 高 (狀態機設計) |
| 可移植性 | 高 | 依賴 STM32 定時器/中斷架構 |