扔掉xshell,基于 QT 实现一个串口命令行工具(带源码)

背景

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/236511.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023年度盘点:智能汽车、自动驾驶、车联网必读书单

【文末送书】今天推荐几本自动驾驶领域优质书籍 前言 2023年&#xff0c;智能驾驶和新能源汽车行业仍然有着肉眼可见的新进展。自动驾驶技术继续尝试从辅助驾驶向自动驾驶的过渡&#xff0c;更重要的是相关技术成本的下降。根据《全球电动汽车展望2023》等行业报告&#xff0c…

【LeetCode刷题】--119.杨辉三角II

119.杨辉三角II class Solution {public List<Integer> getRow(int rowIndex) {List<List<Integer>> nums new ArrayList<List<Integer>>();for(int i 0; i < rowIndex;i){List<Integer> res new ArrayList<Integer>();for(in…

2.DevEco Studio 安装java开发环境

DevEco Studio 安装java开发环境 选择settings

# 一些视觉-激光、加速度传感器类的铣削振动测试方法案例

一些视觉-激光类的铣削振动测试方法 1. 基于激光测振仪的振动测试2. 切削加工的 加速度传感器实测信号2.1 x轴向信号2.2 Y轴向信号3. 关于数值频域积分1. 基于激光测振仪的振动测试 【1】舜宇LDV|激光测振—机床铣刀寿命预测 新刀具为100hz主频 旧刀具为800hz主频 方法原理:…

使用Huggingface创建大语言模型RLHF训练流程的完整教程

ChatGPT已经成为家喻户晓的名字&#xff0c;而大语言模型在ChatGPT刺激下也得到了快速发展&#xff0c;这使得我们可以基于这些技术来改进我们的业务。 但是大语言模型像所有机器/深度学习模型一样&#xff0c;从数据中学习。因此也会有garbage in garbage out的规则。也就是说…

关于加密解密,加签验签那些事

面对MD5、SHA、DES、AES、RSA等等这些名词你是否有很多问号&#xff1f;这些名词都是什么&#xff1f;还有什么公钥加密、私钥解密、私钥加签、公钥验签。这些都什么鬼&#xff1f;或许在你日常工作没有听说过这些名词&#xff0c;但是一旦你要设计一个对外访问的接口&#xff…

Nginx配置文件的基本用法

Nginx简介 1.1概述 Nginx是一个高性能的HTTP和反向代理服务器。 是一款轻量级的高性能的web服务器/反向代理服务器/电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器 单台物理服务器可支持30 000&#xff5e;50 000个并发请求。 1.2Nginx和Apache的优缺点 &#xff…

做数据分析为何要学统计学(9)——什么是回归分析

​回归分析&#xff08;regression analysis)是量化两种或两种以上因素/变量间相互依赖关系的统计分析方法。回归分析根据因素的数量&#xff0c;分为一元回归和多元回归分析&#xff1b;按因素之间依赖关系的复杂程度&#xff0c;可分为线性回归分析和非线性回归分析。我们通过…

记录 | ubuntu源码编译安装/更新boost版本

一、卸载当前的版本 1、查看当前安装的boost版本 dpkg -S /usr/include/boost/version.hpp通过上面的命令&#xff0c;你就可以发现boost的版本了&#xff0c;查看结果可能如下&#xff1a; libboost1.54-dev: /usr/include/boost/version.hpp 2、删除当前安装的boost sudo …

人工智能数据集可视化统计分析工具:快速了解你的数据集

人工智能数据集可视化统计分析工具&#xff1a;快速了解你的数据集 简介特征示例报告安装用法 简介 Lightly Insights&#xff1a;可以轻松获取关于机器学习数据集基本洞察的工具&#xff0c;可以可视化图像数据集的基本统计信息&#xff0c;仅需提供一个包含图像和对象检测标…

perl处理json的序列化和反序列化

perl可以使用JSON模块很方便的处理json的序列化和反序列化。先来一段简单的例子&#xff1a; #! /usr/bin/perl use v5.14; use JSON; use IO::File;my $info {id > 1024,desc > hello world,arry > [1, 2, 3, 4, 5],obj > {char > [ A, B, C ]} };say to_jso…

微服务学习:Nacos微服务架构中的服务注册、服务发现和动态配置Nacos下载

Nacos的主要用途包括&#xff1a; 服务注册与发现&#xff1a;Nacos提供了服务注册和发现的功能&#xff0c;服务提供者可以将自己的服务注册到Nacos服务器上&#xff0c;服务消费者则可以通过Nacos来发现可用的服务实例&#xff0c;从而实现服务调用。 动态配置管理&#xff…

力扣编程题算法初阶之双指针算法+代码分析

目录 第一题&#xff1a;复写零 第二题&#xff1a;快乐数&#xff1a; 第三题&#xff1a;盛水最多的容器 第四题&#xff1a;有效三角形的个数 第一题&#xff1a;复写零 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 思路&#xff1a; 上期…

【媒体邀约】年底企业应该做哪些宣传工作?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 年底是企业进行宣传的好时机&#xff0c;以下是一些建议&#xff1a; 1. 年终总结&#xff1a;发布企业的年度业绩报告、新产品或服务、市场活动等方面的总结&#xff0c;展示企业的成长…

Day06(下) Liunx高级系统设计7-磁盘映射与共享内存

磁盘映射MMAP 概述 存储映射 I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相 映射。于是当从缓冲区中取数据&#xff0c;就相当于读文件中的相应字节。于此类似&#xff0c;将数据存 入缓冲区&#xff0c;则相应的字节就自动写入文件。这样&#xff…

使用JLink仿真器实现调试打印的N种方法

方法一&#xff1a;使用MCU的串口 这是最古老也是最简单的方法。 电脑上面插一个USB转TTL&#xff0c;然后与MCU的UART_RX/UART_TX/GND连接起来。PC端再打开一个串口调试助手。两边的波特率一致&#xff0c;就可以收到MCU发过来的打印信息了。 方法二&#xff1a;使用JLink仿…

10天玩转Python第2天:python判断语句基础示例全面详解与代码练习

目录 1.课程之前1.1 复习和反馈1.2 作业1.3 今日内容1.4 字符串格式化的补充1.5 运算符1.5.1 逻辑运算符1.5.2 赋值运算符1.5.3 运算符优先 2.判断2.1 if 的基本结构2.1.1 基本语法2.1.2 代码案例2.1.3 练习 2.2 if else 结构2.2.1 基本语法2.2.2 代码案例2.2.3 练习 2.3 if 和…

java--BigDecimal

1.BigDecimal 用于解决浮点型运算时&#xff0c;出现结果失真的问题 2.BigDecimal的常见构造器、常用方法

如何使用unittest批量管理Python接口自动化测试用例?

我们日常项目中的接口测试案例肯定不止一个&#xff0c;当案例越来越多时我们如何管理这些批量案例&#xff1f;如何保证案例不重复&#xff1f;如果案例非常多&#xff08;成百上千&#xff0c;甚至更多&#xff09;时如何保证案例执行的效率&#xff1f;如何做&#xff08;批…

Vmware突然无法获取IP(二)

一 测试环境 宿主机&#xff1a; window10Vmware 17 proUbuntu 18.04虚拟机中 二 问题 之前虚拟机可以正常使用。过程中&#xff0c;安装了docker&#xff08;不确定是否和这个有关系&#xff09;第二天开启虚拟机时&#xff0c;发现网口为down的状态。将网口up后&#xff0…