Linux串列埠程式設計進階

2022-07-25 12:02:16

在《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串列埠程式設計的介紹就到這裡了,關於更多更實用的串列埠用法可以隨時交流討論哈~