背景
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相关知识。