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

單片機 C 語言中 while(!TI) 含義詳解:串口發送完成旗標與輪詢等待機制

     編輯:C 語言問答

單片機 C 語言 while(!TI) 完整解析


深入理解 8051 單片機串口通訊中 TI (Transmit Interrupt) 旗標的運作機制,以及 while(!TI) 輪詢等待發送完成的程式設計模式。


TI 旗標基本定義

屬性說明
全名Transmit Interrupt Flag (發送中斷旗標)
所屬暫存器SCON (串口控制暫存器) 的第 1 位 (bit 1)
硬體自動置位當串口完成一個位元組發送後,硬體自動將 TI 設為 1
軟體必須清除CPU 必須在程式中手動將 TI 清 0,準備下一次發送
初始值複位後為 0

while(!TI) 逐字解析

邏輯拆解

TI = 0  →  !TI = 1 (真)  →  while 迴圈持續執行 (等待)
TI = 1  →  !TI = 0 (假)  →  while 迴圈結束,繼續往下執行

完整發送流程

  1. 寫入資料:SBUF = data; (啟動硬體發送)
  2. 輪詢等待:while(!TI); (阻塞直到發送完成)
  3. 清除旗標:TI = 0; (手動清除,準備下次發送)

標準發送函式範例

void UART_SendByte(unsigned char dat) {
    SBUF = dat;        // 將資料寫入發送緩衝器,啟動發送
    while(!TI);        // 等待發送完成 (TI 變為 1)
    TI = 0;            // 必須手動清除 TI 旗標
}

void UART_SendString(char *str) {
    while(*str) {
        UART_SendByte(*str++);
    }
}

為什麼需要輪詢等待?

串口發送特性

  • 非同步操作:寫入 SBUF 後,硬體獨立進行位元逐位發送
  • 發送需時:波特率 9600 時,發送 1 位元組約需 1ms
  • 緩衝區單一:SBUF 同一時間只能容納 1 位元組
  • 覆寫風險:若未等待完成即寫入新資料,前一筆資料會遺失

時序圖解

時間軸 →
CPU:  SBUF=資料A  ──────────────────────  SBUF=資料B
       │              ↑                   │
       │           等待                   │
硬體:  發送資料A ──────→ TI=1 (發送完成)
       ↑
    此時 TI=0,while(!TI) 成立,CPU 卡在迴圈
    
硬體:  發送資料B ──────→ TI=1
CPU:                    TI=0 (清除旗標) → 繼續執行

進階變體與最佳實務

帶超時保護的輪詢 (防止死鎖)

#define UART_TIMEOUT 10000

bit UART_SendByte_Timeout(unsigned char dat) {
    unsigned int timeout = 0;
    SBUF = dat;
    while(!TI) {
        if(++timeout > UART_TIMEOUT) {
            return 0;  // 發送超時,返回失敗
        }
    }
    TI = 0;
    return 1;  // 發送成功
}

中斷驅動發送 (非阻塞)

// 全域緩衝區
unsigned char tx_buffer[TX_BUFFER_SIZE];
unsigned char tx_head = 0, tx_tail = 0, tx_count = 0;

// 發送函式 (非阻塞)
void UART_SendByte_ISR(unsigned char dat) {
    if(tx_count < TX_BUFFER_SIZE) {
        tx_buffer[tx_head++] = dat;
        tx_head %= TX_BUFFER_SIZE;
        tx_count++;
        ES = 1;  // 啟用串口中斷
    }
}

// 串口中斷服務程式
void UART_ISR() interrupt 4 {
    if(TI) {
        TI = 0;
        if(tx_count > 0) {
            SBUF = tx_buffer[tx_tail++];
            tx_tail %= TX_BUFFER_SIZE;
            tx_count--;
        } else {
            ES = 0;  // 緩衝區空,關閉發送中斷
        }
    }
    if(RI) {
        RI = 0;
        // 接收處理...
    }
}

比較:輪詢 vs 中斷

特性輪詢 while(!TI)中斷驅動
程式複雜度簡單較複雜
CPU 佔用阻塞等待,佔用 CPU非阻塞,CPU 可執行其他任務
即時性發送期間無法回應其他事件可即時回應中斷/其他任務
適用場景簡單應用、資料量少、無即時需求複雜系統、即時控制、大量資料傳輸
程式碼大小小較大

常見錯誤與除錯

錯誤現象可能原因解決方案
程式卡在 while(!TI) 無法繼續1. 波特率設定錯誤
2. 晶振頻率不符
3. 串口模式設定錯誤
4. 硬體連線問題
檢查 TH1/TL1 設定、確認晶振、檢查 SM0/SM1、用示波器測 TX 腳位
資料發送錯誤/亂碼1. 未清除 TI 旗標
2. 波特率誤差過大
3. 位元格式不符 (資料位、停止位、校驗位)
確保 TI=0、校準波特率、檢查 SM0/SM1/SM2 設定
連續發送資料遺失未等待前一筆發送完成即寫入 SBUF務必使用 while(!TI) 等待

相關旗標:RI (接收中斷旗標)

旗標全名觸發條件應用
TITransmit Interrupt發送完成while(!TI) 等待發送
RIReceive Interrupt接收完成while(!RI) 等待接收 / 中斷接收

接收對應範例

unsigned char UART_ReceiveByte() {
    while(!RI);    // 等待接收完成
    RI = 0;        // 清除接收旗標
    return SBUF;   // 讀取接收資料
}

總結

  • TI 是串口發送完成的硬體旗標
  • while(!TI) 實作輪詢等待,阻塞 CPU 直到發送完成
  • 發送完成後必須手動清除 TI = 0
  • 簡單應用用輪詢,複雜系統建議用中斷驅動 + 環狀緩衝區
  • 務必加入超時保護避免死鎖
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved