萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 作業系統 >  >> Linux 教學

Linux 序列埠程式設計完全指南:termios 結構體配置與讀寫實戰

Linux 序列埠(Serial Port)程式設計核心在於 struct termios 結構體配置。本文系統整理 UART 基礎、termios 旗標詳解、標準配置流程、完整可執行範例代碼,適用於嵌入式開發、工業控制、Modbus 通訊、GPS/GNSS 接收等場景。

序列通訊基礎知識

參數常見值說明
波特率9600, 19200, 38400, 57600, 115200, 230400, 460800位元傳輸速率
資料位5, 6, 7, 8通常 8 位元
停止位1, 1.5, 2通常 1 位元
奇偶校驗None, Even, Odd, Mark, Space通常 None
流控制None, RTS/CTS (硬體), XON/XOFF (軟體)通常 None

裝置節點:/dev/ttyS0 (COM1)、/dev/ttyS1 (COM2)、/dev/ttyUSB0 (USB 轉序列)、/dev/ttyACM0 (CDC ACM 設備)

termios 結構體完整定義

#include 

struct termios {
    tcflag_t c_iflag;   // 輸入模式旗標
    tcflag_t c_oflag;   // 輸出模式旗標
    tcflag_t c_cflag;   // 控制模式旗標 (核心:波特率、資料位、停止位、校驗)
    tcflag_t c_lflag;   // 本地模式旗標 (規範/非規範模式、回顯、訊號)
    cc_t c_cc[NCCS];    // 控制字元陣列 (VMIN, VTIME 等)
    speed_t c_ispeed;   // 輸入波特率 (Linux 通常同 c_cflag)
    speed_t c_ospeed;   // 輸出波特率
};

序列埠配置示意圖

關鍵旗標位定義

c_cflag — 控制模式(硬體參數)

旗標功能常用值
CBAUD波特率掩碼B9600, B115200, B460800 等
CSIZE資料位掩碼CS5, CS6, CS7, CS8
CSTOPB兩停止位設定=2 位,清除=1 位
PARENB啟用奇偶校驗配合 PARODD 設定奇/偶
PARODD奇校驗設定=奇,清除=偶(需 PARENB)
HUPCL關閉時掛斷線通常設定
CLOCAL忽略數據線載波偵測必須設定(本地連接)
CREAD啟用接收器必須設定
CRTSCTS硬體流控 (RTS/CTS)需硬體支援時設定

c_iflag — 輸入模式

旗標功能
IGNBRK忽略中斷條件
BRKINT中斷產生 SIGINT
IGNPAR忽略奇偶錯誤
PARMRK標記奇偶錯誤
INPCK啟用輸入奇偶檢查
ISTRIP剝離第 8 位
INLCR/IGNCR/ICRNLNL/CR 轉換處理
IXON/IXOFF/IXANY軟體流控 (XON/XOFF)

c_lflag — 本地模式(規範模式控制)

原始模式關鍵:c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG)

  • ICANON:規範模式(行緩衝、編輯)、關閉→原始模式(字元級讀取)
  • ECHO:回顯輸入字元
  • ISIG:啟用 INTR/QUIT/SUSP 訊號字元

c_cc — 控制字元(非規範模式關鍵)

索引含義典型值
VMIN最小讀取字元數0 (非阻塞), 1 (阻塞至 1 字元), >1 (阻塞至 N 字元)
VTIME讀取逾時 (0.1 秒單位)0 (無逾時), 10 (1 秒), 等等

VMIN/VTIME 四種組合行為:

VMINVTIME行為
>0>0讀取最多 VMIN 字元,逾時 VTIME 滴答返回已讀數
>0=0阻塞直到讀到 VMIN 字元
=0>0非阻塞輪詢,最多等待 VTIME,有資料即返回
=0=0完全非阻塞,立即返回(0 字元或可用字元)

標準配置流程(9 步驟)


int serial_init(int fd, int baud, int databits, char parity, int stopbits) {
    struct termios newtio, oldtio;

    // 1. 保存原配置
    if (tcgetattr(fd, &oldtio) != 0) {
        perror("tcgetattr");
        return -1;
    }

    // 2. 清零新配置
    bzero(&newtio, sizeof(newtio));

    // 3. 啟用本地連接與接收
    newtio.c_cflag |= CLOCAL | CREAD;

    // 4. 設定資料位
    newtio.c_cflag &= ~CSIZE;
    switch (databits) {
        case 7: newtio.c_cflag |= CS7; break;
        case 8: newtio.c_cflag |= CS8; break;
        default: return -1;
    }

    // 5. 設定奇偶校驗
    switch (parity) {
        case 'O': case 'o':  // 奇校驗
            newtio.c_cflag |= PARENB | PARODD;
            newtio.c_iflag |= INPCK | ISTRIP;
            break;
        case 'E': case 'e':  // 偶校驗
            newtio.c_cflag |= PARENB;
            newtio.c_cflag &= ~PARODD;
            newtio.c_iflag |= INPCK | ISTRIP;
            break;
        case 'N': case 'n':  // 無校驗
            newtio.c_cflag &= ~PARENB;
            break;
        default: return -1;
    }

    // 6. 設定波特率
    speed_t speed;
    switch (baud) {
        case 2400: speed = B2400; break;
        case 4800: speed = B4800; break;
        case 9600: speed = B9600; break;
        case 19200: speed = B19200; break;
        case 38400: speed = B38400; break;
        case 57600: speed = B57600; break;
        case 115200: speed = B115200; break;
        case 230400: speed = B230400; break;
        case 460800: speed = B460800; break;
        default: speed = B9600;
    }
    cfsetispeed(&newtio, speed);
    cfsetospeed(&newtio, speed);

    // 7. 設定停止位
    if (stopbits == 1)
        newtio.c_cflag &= ~CSTOPB;
    else if (stopbits == 2)
        newtio.c_cflag |= CSTOPB;
    else
        return -1;

    // 8. 非規範模式:最小字元、逾時
    newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    newtio.c_cc[VMIN] = 1;   // 至少讀 1 字元
    newtio.c_cc[VTIME] = 10; // 1 秒逾時

    // 9. 清空緩衝區並套用
    tcflush(fd, TCIFLUSH);
    if (tcsetattr(fd, TCSANOW, &newtio) != 0) {
        perror("tcsetattr");
        return -1;
    }
    return 0;
}

完整可執行範例:Modbus RTU 主站輪詢

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define DEVICE "/dev/ttyUSB0"
#define BAUD 9600

int main() {
    int fd = open(DEVICE, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    fcntl(fd, F_SETFL, 0);  // 清除非阻塞旗標

    if (serial_init(fd, BAUD, 8, 'N', 1) < 0) {
        close(fd);
        return 1;
    }

    // Modbus RTU 讀保持暫存器 (功能碼 0x03)
    // 從站 1, 起始位址 0, 數量 2
    unsigned char req[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x02, 0xC4, 0x0B}; // 含 CRC
    unsigned char resp[256];

    while (1) {
        // 發送請求
        write(fd, req, sizeof(req));
        tcdrain(fd);  // 等待發送完成

        // 讀取回應 (預期 7 字節: 從站+功能+位元組數+2暫存器*2+CRC)
        int n = read(fd, resp, sizeof(resp));
        if (n > 0) {
            printf("RX [%d]: ", n);
            for (int i = 0; i < n; i++) printf("%02X ", resp[i]);
            printf("\n");
        } else if (n < 0 && errno != EAGAIN) {
            perror("read");
            break;
        }
        usleep(1000000); // 1 秒輪詢間隔
    }

    close(fd);
    return 0;
}

除錯技巧與工具

工具/指令用途
stty -F /dev/ttyUSB0 -a查看當前序列埠所有參數
stty -F /dev/ttyUSB0 115200 cs8 -cstopb -parenb快速設定參數
minicom -D /dev/ttyUSB0 -b 115200互動式終端機
screen /dev/ttyUSB0 115200簡易終端機
socat - /dev/ttyUSB0,b115200雙向管道、記錄、TCP 轉發
cat /dev/ttyUSB0原始資料流查看
ls -l /dev/ttyS* /dev/ttyUSB* /dev/ttyACM*列舉可用序列埠
dmesg | grep tty查看核心驅動載入訊息

常見問題排查

現象原因解決
收到亂碼波特率不匹配雙端確認相同波特率、用示波器/邏輯分析儀測量
無資料接收接收未啟用/接線錯誤檢查 CREAD、TX/RX 交叉、GND 共地
發送無回應流控制衝突/協議錯誤關閉 CRTSCTS、檢查協議幀格式、CRC
讀取阻塞不返回VMIN/VTIME 設定不當非規範模式設定 VMIN=1, VTIME=10
權限被拒用戶不在 dialout 群組usermod -a -G dialout $USER 重新登入

小結

Linux 序列埠程式設計核心是 termios 結構體的精確配置。掌握 c_cflag 硬體參數、c_lflag 原始模式、c_cc 讀取行為 三大支柱,配合標準 9 步驟初始化流程,即可構建穩健可靠的序列通訊程式。實戰中善用 stty、minicom、socat 輔助除錯,記住:接線正確是前提,參數匹配是核心,協議正確是保證。

copyright © 萬盛學電腦網 all rights reserved