Linux串口編程進階

在《Linux串口編程》編程一文中介紹了串口應用中常用的基本操作,如:串口打開關閉、串口設置、數據收發等。本篇文章主要基於常規串口操作進行了擴充,主要介紹如下操作:

  • Linux系統使用非標準波特率
  • 同步方式串口發送
  • select I/O復用串口數據讀寫
  • 串口參數VTIME和VMIN的作用
  • RS485串口功能應用
  • 串口同步等待Modem訊號變化

與上一篇文章類似,為方便用戶使用我們將以上串口操作均封裝成了獨立的函數,可以極大的節約開發時間。

1、Linux系統使用非標準波特率

    /**
     * libtty_setcustombaudrate - set baud rate of tty device
     * @fd: device handle
     * @speed: baud rate to set
     *
     * The function return 0 if success, or -1 if fail.
     */
    static int libtty_setcustombaudrate(int fd, int baudrate)
    {
    	struct termios2 tio;
     
    	if (ioctl(fd, TCGETS2, &tio)) {
    		perror("TCGETS2");
    		return -1;
    	}
     
    	tio.c_cflag &= ~CBAUD;
    	tio.c_cflag |= BOTHER;
    	tio.c_ispeed = baudrate;
    	tio.c_ospeed = baudrate;
     
    	if (ioctl(fd, TCSETS2, &tio)) {
    		perror("TCSETS2");
    		return -1;
    	}
     
    	if (ioctl(fd, TCGETS2, &tio)) {
    		perror("TCGETS2");
    		return -1;
    	}
     
    	return 0;
    }

Note:

  1. 使用cfsetspeed函數集無法設置非標準波特率的主要原因主要是因為系統中缺少相關的宏定義。glibc中均是通過波特率宏和實際波特率之間進行轉換。
  2. 如上設置波特率的方法主要是使用了termios2結構體,將相應標誌位BOTHER置為有效,然後通過ioctl傳遞給驅動的tty核心層。

2、同步方式串口發送

直接使用write函數完成串口數據的發送時,在該函數返回時實際上只是把write緩衝區的數據拷貝至tty內核層的緩衝區中,當緩衝區滿時write才會阻塞,此過程中串口驅動並未執行真正的發送動作。在有些場景下,我們希望等待串口發送物理上真正完成了再執行後續的操作。那麼此時需要使用的函數為:

    /**
     * tcdrain() waits until all output written to the object referred to by fd has been     
     * transmitted.
     */
    int tcdrain(int fd);

3、select I/O復用串口數據讀寫

使用select函數實現的I/O多路轉接模型,在select操作期間,I/O可以進行其他操作。在對多個設備同時使用的應用場景中應用較為普遍。比如多個串口設備,或者網路通訊中處理多個客戶端。select可以具體設置每個文件描述符的條件、等待時間等,這樣在函數返回時可以知道具體哪個設備已經準備好讀寫。

    /**
     * libtty_selectread - read data from uart
     * @fd: device handle
     * @buffer: pointer to read buffer
     * @count: read length
     *
     * The function return actual read length if success, 0 if timeout, -1 if fail.
     */
    static int libtty_selectread(int fd, char *buffer, int count)
    {
        int ret;
    	fd_set rd;
        struct timeval tv;
     
        FD_ZERO(&rd);
        FD_SET(fd, &rd);
     
        tv.tv_sec = 5;
    	tv.tv_usec = 0;
     
        ret = select(fd + 1, &rd, NULL, NULL, &tv);
        if (ret == -1) {
            perror("select(): ");
    	}
        else if (ret)
            return read(fd, buffer, count);
        else {
            printf("select timeout.\n");
        }
        return ret;
    }

4、串口參數VTIME和VMIN的作用

VTIME和VMIN常規情況下,設置為0。但是很多應用場景我們需要將二者結合起來共同控制對串口的讀取行為,參數組合說明如下:

  • VMIN = 0 和 VTIME = 0 :在這種情況下,read 調用總是立刻返回。如果有等待處理的字元,read 就會立刻返回;如果沒有字元等待處理,read 調用返回0,並且不讀取任何字元;
  • VMIN = 0 和 VTIME > 0 :在這種情況下,只要有字元可以處理或者是經過 VTIME 個十分之一秒的時間間隔,read 調用就返回。如果因為超時而未讀到任何字元,read 返回0,否則 read 返回讀取的字元數目。
  • VMIN > 0 和 VTIME = 0 :在這種情況下,read 調用將一直等待,直到有 MIN 個字元可以讀取時才返回,返回值是讀取的字元數量。到達文件尾時返回0。
  • VMIN > 0 和 VTIME > 0 :在這種情況下,當 read 被調用時,它會等待接收一個字元。在接收到第一個字元及後續的每個字元後,一個字元間隔定時器被啟動(如果定時器已經運行,則重啟它)。當有 MIN 個字元可讀或兩個字元之間的時間間隔超過 TIME 個十分之一秒時,read 調用返回。這個功能可用於區分是單獨按下了 Escape 鍵還是按下一個 Escape 鍵開始的功能組合鍵。但要注意的是,網路通訊或處理器的高負載將使得類似這樣的定時器失去作用。

5、RS485串口功能應用

部分使用RS485的應用場景或者針對特定的串口硬體,需要通過串口應用程式主動調用RS485功能開啟的相關API。用法如下:

    /**
     * libtty_rs485set - rs485 set
     * @fd: file descriptor of tty device
     * @enable: 0 on disable, other on enable
     *
     * The function return 0 if success, others if fail.
     */
    int libtty_rs485set(int fd, char enable)
    {
    	struct serial_rs485 rs485conf;
    	
    	if (enable)
    		rs485conf.flags |= SER_RS485_ENABLED;
    	else
    		rs485conf.flags &= ~SER_RS485_ENABLED;
    	
    	return ioctl(fd, TIOCSRS485, &rs485conf);
    }

6、 串口同步等待Modem訊號變化

同步等待串口Modem訊號變化是指,應用程式可以調用介面函數進入等待,直到程式中設定的Modem輸入訊號DCD/RI/CTS/DCD的訊號變化才退出等待。常用於設備狀態與特定儀器的操作同步場景。

    /**
     * libtty_tiocmwait - wiat for modem signal to changed
     * @fd: file descriptor of tty device
     *
     * The function return 0 if success, others if fail.
     */
    static int libtty_tiocmwait(int fd)
    {
    	unsigned long modembits = TIOCM_DSR | TIOCM_CTS | TIOCM_CD | TIOCM_RI;
     
    	return ioctl(fd, TIOCMIWAIT, modembits);
    }

如以上程式碼所示,設置等待的modem訊號為所有輸入訊號DSR、CTS、DCD和RI,只要任意一路串口訊號發生改變,則API退出。

關於Linux串口編程的介紹就到這裡了,關於更多更實用的串口用法可以隨時交流討論哈~