背景
xshell 帶有支持串口的命令行能力, 可以方便的和下位機用命令進行交互,如下圖所示:
msh >
msh >
msh >version\ | /
- RT - Thread Operating System/ | \ 3.1.3 build Nov 7 20232006 - 2019 Copyright by rt-thread team
msh >
msh >
msh >
假設有這樣一種使用場景,我們經常會使用串口調試助手連接串口進行16進制或者ascii的數據調試,但同時又想使用命令行工具下發指令,比如查看文件夾等等。因為串口是獨占式連接,所以我們就必須關閉串口調試助手的串口連接,再打開xshell連接,沒辦法做到同時使用。
假如有這種使用訴求,那作為程序員我們就有必要在一個軟件同時實現這兩個功能,則這兩個功能就可以同時使用了。所以本文重點是如何實現串口命令行,關于串口調試助手的功能比較簡單,就不再說明。
關鍵知識點
原理說明
不同于常見的比如windows的cmd命令行,linux的shell終端,或其他bash環境等等,他們是一個指令作為一個單元發送給下位機,比如:ls
,上位機會將"ls"整個單詞加上結束符"\r\n"發送給下位機處理。而串口命令行有一個特點是逐字符發送和顯示,比如"ls" 會先發送 “l” ,然后下位機回復"l",上位機收到"l"進行顯示。上位機再發送"s",下位機再回復"s",上位機收到"s" 進行顯示。最后當用戶敲下回車鍵時,上位機發送 “\r\n”(只是舉例說明),下位機此時會解析整條指令,并將處理好的數據返回給上位機,上位機簡單處理后進行顯示。所以基于串口的命令行工具有個特點是:如果串口連接不正常或者串口正常但是下位機程序運行不正常,通過上位機發送的命令下位機無法回復,則上位機不顯示任何東西(因為沒有收到下位機的回復)。
經過調研發現mcu的命令行解析工具都是基于逐字符方式實現的,比如 finsh、letter shell等,個人猜測這樣做的目的可能是因為下位機設備的資源限制或者uart的限制?或者說實時性? 有知道的同學可以評論區回答一下。
關鍵鍵值
詳見:ASCII碼一覽表,ASCII碼對照表
ASCII 編碼中第 0~31 個字符(開頭的 32 個字符)以及第 127 個字符(最后一個字符)都是不可見的(無法顯示),但是它們都具有一些特殊功能,所以稱為控制字符( Control Character)或者功能碼(Function Code)。這 33 個控制字符大都與通信、數據存儲以及老式設備有關。
不可見的意思就是無法在屏幕上顯示出來,但是代碼中可以用char表示。比如 tab 鍵對應的 \t
,如果非要顯示的話,只能當作常規的字符串 一個反斜杠+一個字母 t 進行顯示,而無法代表其本身的意思。
剩下的95個字符就是我們常見的比如:0-9,a-z,A-Z等,這些字符可以被識別和顯示,也就是用戶可以輸入并顯示出來,可以被作為傳輸字符來使用。所以對于我們的程序來講,需要特殊處理的字符就是33個字符,當然并不是所有,我們只需要處理我們常見的支持的字符即可,比如回車符、制表符等。而其他的字符作為用戶輸入的指令進行下發和回顯即可。
常見的鍵對應的指令如:
/** handle control key* up key : 0x1b 0x5b 0x41* down key: 0x1b 0x5b 0x42* right key:0x1b 0x5b 0x43* left key: 0x1b 0x5b 0x44*//* received null or error */ch == '\0' || ch == 0xFF/* handle tab key */ch == '\t'/* handle backspace key */(ch == 0x7f || ch == 0x08)/* handle end of line, break */ch == '\r' || ch == '\n'
關鍵代碼
void QVTerminal::keyPressEvent(QKeyEvent* event)
{QByteArray data;switch (event->key()) {case Qt::Key_Up://char bytes[3] = {0x1b, 0x5b, 0x41};data.append("\033[A");break;case Qt::Key_Down:data.append("\033[B");break;case Qt::Key_Right:data.append("\033[C");break;case Qt::Key_Left:data.append("\033[D");break;case Qt::Key_Home:data.append('\x01');break;case Qt::Key_End:data.append('\x05');break;case Qt::Key_Tab:data.append('\t');break;case Qt::Key_Backspace:data.append('\b');break;case Qt::Key_Return:data.append('\n');break;default:data.append(event->text().toUtf8());QAbstractScrollArea::keyPressEvent(event);}emit transmitData(data);
}
這是按鍵發送的核心代碼,比如我們輸入"version",并按下回車,用串口抓包助手(推薦CommMonitor10.0.3版本,免費)可以看到下位機收到的數據和回復的數據:
COM5,Wirte(1): 76 | v
COM5, Read(1): 76 | v
COM5,Wirte(1): 65 | e
COM5, Read(1): 65 | e
COM5,Wirte(1): 72 | r
COM5, Read(1): 72 | r
COM5,Wirte(1): 73 | s
COM5, Read(1): 73 | s
COM5,Wirte(1): 69 | i
COM5, Read(1): 69 | i
COM5,Wirte(1): 6F | o
COM5, Read(1): 6F | o
COM5,Wirte(1): 6E | n
COM5, Read(1): 6E | n
COM5,Wirte(1): 0D | \#13
COM5, Read(32): 0D 0A 0D 0A 20 5C 20 7C 20 2F 0D 0A 2D 20 52 54 20 2D 20 20 20 20 20 54 68 72 65 61 64 20 4F 70 | \#13\#10\#13\#10 \ | /\#13\#10- RT - Thread Op
COM5, Read(64): 65 72 61 74 69 6E 67 20 53 79 73 74 65 6D 0D 0A 20 2F 20 7C 20 5C 20 20 20 20 20 33 2E 31 2E 33 20 62 75 69 6C 64 20 4E 6F 76 20 20 37 20 32 30 32 33 0D 0A 20 32 30 30 36 20 2D 20 32 30 31 39 | erating System\#13\#10 / | \ 3.1.3 build Nov 7 2023\#13\#10 2006 - 2019
COM5, Read(32): 20 43 6F 70 79 72 69 67 68 74 20 62 79 20 72 74 2D 74 68 72 65 61 64 20 74 65 61 6D 0D 0A 6D 73 | Copyright by rt-thread team\#13\#10ms
COM5, Read(3): 68 20 3E | h >
可以看到我們write一個字符,下位機就回復一個字符,直到我們發送"0D",也就是Enter鍵"\r",下位機才會返回這個指令的最終響應數據。
下面的代碼是收到下位機數據后的處理:
void QVTerminal::appendData(const QByteArray& data)
{QByteArray text;setUpdatesEnabled(false);QByteArray::const_iterator it = data.cbegin();while (it != data.cend()) {QChar c = *it;switch (state) {case QVTerminal::Text:switch (c.unicode()) {case '\033':appendString(text);text.clear();state = QVTerminal::Escape;break;case '\r':appendString(text);text.clear();cursorPos.setX(0);break;case '\n':appendString(text);text.clear();moveCursor(0, 1);break;case '\b':appendString(text);text.clear();moveCursor(-1, 0);break;default:if (c.isPrint()) {text.append(c);}}break;case QVTerminal::Escape:formatValue = 0;if (c == '[') {state = QVTerminal::Format;} else if (c == '(') {state = QVTerminal::ResetFont;}break;case QVTerminal::Format:if (c >= '0' && c <= '9') {formatValue = formatValue * 10 + (c.cell() - '0');} else {formatChar(c);state = QVTerminal::Text;}break;case QVTerminal::ResetFont:curentFormat = format;state = QVTerminal::Text;break;}it++;}appendString(text);verticalScrollBar()->setRange(0, ch * (layout->lineCount() + 1) - viewport()->size().height());verticalScrollBar()->setValue(verticalScrollBar()->maximum());setUpdatesEnabled(true);update();
}
下載地址: https://download.csdn.net/download/u012534831/88619133
其他代碼我打包上傳到csdn資源中,關注公號后在后臺留言需要下載的資源,我看到后免費發給你,并可以得到我的免費解答。 原創不易,謝謝支持。
關注公眾號 QTShared,帶你探索更多QT相關知識。