Ubuntu下串列埠工具 PicoCOM 的使用和時間戳顯示

2023-05-14 21:00:36

PICOCOM

Ubuntu下的串列埠軟體, 除了 CuteCOM, screen, MiniCOM 以外, 還有一個和 MiniCOM 很像的 PicoCOM. 最近在偵錯 CH340C 串列埠的過程中, 發現只有 PicoCOM 的連線Reset才能正常工作, 因此單獨記錄一下.

安裝

在 Ubuntu 上直接通過sudo apt install picocom安裝, 版本是3.1

使用

連線和斷開

使用 115200 波特率連線串列埠裝置 /dev/ttyUSB0

picocom -b115200 /dev/ttyUSB0

斷開有兩種方式

  • Ctrl+A, Ctrl+Q 退出, NO RESET
  • Ctrl+A, Ctrl+X 退出, RESET

在串列埠通訊時, RTS(Ready To Send)會處於低電平, 當斷開串列埠時如果RESET, 就會恢復高電平, 預設會進行RESET.

在偵錯STC微控制器的時候, 往往會使用帶自動燒錄的串列埠轉USB裝置連線, 而這種裝置的自動燒錄機制, 就是通過拉低RTS觸發MCU斷電重啟. 在使用這種RTS觸發的裝置進行燒錄和偵錯時, 就要靈活使用RESET機制.

  • 當進行偵錯時, 我們不希望每次連線MCU時自動重啟, 因此斷開串列埠時要避免RESET, 讓RTS一直處於低電平, 斷開連線時使用 Ctrl+A Ctrl+Q 退出, 可以避免RESET
  • 當進行燒錄時, 如果RTS還處於低電平, 拉低RTS無效, 導致無法重啟MCU. 所以在燒錄時, 斷開串列埠要RESET, 讓RTS回到高電平, 使用 Ctrl+A Ctrl+X 退出

PS: 這個功能在 picocom 上工作是正常的, 但是在 minicom 上工作不正常, 使用 Ctrl+A, Q 退出一次之後, 無論再使用 Ctrl+A,X, 還是 Ctrl+A,Q 都無法再觸發 RESET

顯示二進位制

在偵錯串列埠通訊時, 有時候需要觀察串列埠的二進位制輸出, 這時候就需要將資料通過16進位制列印出來, 在 picocom 下需要用 --imap 引數, 這個引數是一個多選項, 可以區分不同型別的數值進行轉換.

--imap <map> (input mappings)
<map> is a comma-separated list of one or more of ...

例如

picocom --imap spchex,tabhex,crhex,lfhex,nrmhex,8bithex -b 19200 /dev/ttyS0

各引數的說明

  • spchex (map special chars (< 0x20 || 0x7f), excl. CR, LF, and TAB to hex)
  • tabhex (map TAB to hex)
  • crhex (map CR to hex)
  • lfhex (map LF to hex)
  • 8bithex (map chars with 8th-bit set to hex)
  • nrmhex (map normal ascii chars (0x20 <= c < 0x7f) to hex)

二次開發: 增加時間戳

偵錯串列埠時經常會用到時間戳, 例如觀察延時是否正確. 雖然這個時間戳並不準確, 但是作為一個粗略的時間標記還是非常方便的. minicom在較新的版本中已經支援時間戳輸出, 但是 picocom 上還沒有這個功能.

開源軟體的好處就在於, 用得不順手可以自己改. 在 picocom 上增加時間戳輸出也並不麻煩.

先通過專案倉庫 https://github.com/npat-efault/picocom 匯出程式碼, 直接make編譯驗證環境是否正確. 如果編譯不成功先解決編譯環境問題.

而後是修改程式碼, 主要修改的部分都在 picocom.c

標頭檔案引入 sys/time.h, 因為會用到時間取值函數

#include <sys/time.h>

增加定義, n/N 這個引數用於開關/選擇時間戳格式

#define KEY_TIMESTAMP CKEY('n') /* show timestamp */

在結構體 struct { ... } opts 中增加一個欄位 int timestamp; 用於記錄時間戳選項

在幫助顯示中增加按鍵提示

    fd_printf(STO, "*** [C-%c] : Toggle display timestamp\r\n",
              KEYC(KEY_TIMESTAMP));

增加對應的按鍵處理

    case KEY_TIMESTAMP:
        opts.timestamp = (opts.timestamp + 1) % 4;
        fd_printf(STO, "\r\n*** display timestamp, format:%d ***\r\n",
                  opts.timestamp);

在輸出部分, 增加時間戳的生成方法, 這裡會產生4種顯示方式, 0:不顯示, 1:分:秒, 2:時:分:秒, 3:年-月-日 時:分:秒

/* print leading timestamp */
void print_lead_str(void)
{
    struct timeval tv;
    struct tm lt = {0};
    char buff[32], buff2[64];

    gettimeofday(&tv, NULL);
    localtime_r(&(tv.tv_sec), &lt);

    switch (opts.timestamp) {
    case 3:
        strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", &lt);
        sprintf(buff2, "%s.%03ld ", buff, tv.tv_usec / 1000);
        write(STO, buff2, 24);
        break;
    case 2:
        strftime(buff, sizeof(buff), "%H:%M:%S", &lt);
        sprintf(buff2, "%s.%03ld ", buff, tv.tv_usec / 1000);
        write(STO, buff2, 13);
        break;
    case 1:
        strftime(buff, sizeof(buff), "%M:%S", &lt);
        sprintf(buff2, "%s.%03ld ", buff, tv.tv_usec / 1000);
        write(STO, buff2, 10);
        break;
    default:
        break;
    }
}

在顯示中增加呼叫

        if ( FD_ISSET(tty_fd, &rdset) ) {

            char buff_rd[TTY_RD_SZ];
            char buff_map[TTY_RD_SZ * M_MAXMAP];

            /* read from port */

            do {
                n = read(tty_fd, &buff_rd, sizeof(buff_rd));
            } while (n < 0 && errno == EINTR);
            if (n == 0) {
                fatal("read zero bytes from port");
            } else if ( n < 0 ) {
                if ( errno != EAGAIN && errno != EWOULDBLOCK )
                    fatal("read from port failed: %s", strerror(errno));
            } else {
                print_lead_str();                       //<--- 輸出時間戳
                int i;
                char *bmp = &buff_map[0];
                if ( opts.log_filename )
                    if ( writen_ni(log_fd, buff_rd, n) < n )
                        fatal("write to logfile failed: %s", strerror(errno));
                for (i = 0; i < n; i++) {
                    bmp += do_map(bmp, opts.imap, buff_rd[i]);
                }
                n = bmp - buff_map;
                if ( writen_ni(STO, buff_map, n) < n )
                    fatal("write to stdout failed: %s", strerror(errno));
            }
        }

詳細的程式碼改動可以參考

https://github.com/IOsetting/picocom

除了增加了 -N 引數顯示時間戳功能, 還修改了預設的通訊波特率, 將 9600 改為 115200, 因為現在基本上都是 115200 了. 執行make編譯後可以直接使用.

在通訊過程中通過 Ctrl+A Ctrl+N 依次切換不同顯示格式, 也可以在啟動時直接指定, 例如

./picocom --imap nrmhex,8bithex /dev/ttyUSB0 -N3

參考