首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

STM32F4xx 中斷驅動 GPIO 模擬 I2C 總線:狀態機實現與回調機制

     編輯:關於C語言

設計動機

主流 GPIO 模擬 I2C 多為輪詢延時,佔用大量 CPU 周期。本實現採用定時器中斷推動狀態機,實現非阻塞、後台自動化的 I2C 主機控制器。

架構概覽

  • 支援 3 路獨立 I2C (I2C1/2/3),引腳可配置
  • 定時器中斷驅動:TIM5/6/7 對應 3 路,中斷頻率設定總線速率 (最高 1MHz)
  • 狀態機自動推進:START → 發送地址 → 讀寫數據 → ACK/NAK 處理 → STOP
  • 回調機制:完成/錯誤觸發用戶回調,主循環無需輪詢
  • 兼容查詢模式:可輪詢 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;

關鍵時序實現 (節選)

START 條件


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 表示完成
}

字節發送 (含 ACK 接收)


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;
}

高層讀流程 (7位地址、1字節寄存器地址、多字節讀取)


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); // 或在回調中設置標誌

中斷服務程序 (ISR)


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 定時器/中斷架構

適用場景

  • 多任務系統 (FreeRTOS/RT-Thread) 下 I2C 設備驅動
  • 高頻采集傳感器 (IMU、溫濕度、EEPROM) 需非阻塞讀取
  • 電池供電設備,需定時器關閉省電
  • 教學理解 I2C 時序與狀態機建模
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved