背景
xshell 带有支持串口的命令行能力, 可以方便的和下位机用命令进行交互,如下图所示:
msh >
msh >
msh >version
\ | /
- RT - Thread Operating System
/ | \ 3.1.3 build Nov 7 2023
2006 - 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相关知识。