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

news2024/12/24 15:53:03

背景

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《opencv实用探索·十四》VideoCapture播放视频和视像头调用

1、VideoCapture播放视频 #include <opencv2/opencv.hpp> #include <iostream>using namespace std; using namespace cv;int main() {// 定义相关VideoCapture对象VideoCapture capture;// 打开视频文件capture.open("1.avi");// 判断视频流读取是否正…

聚首引领行业风潮!聚首品牌联动资源价值平台发布会正式启航

2023年12月10日&#xff0c;由杭州建筑装饰学会、浙江聚首联优材料科技有限公司主办&#xff0c;天尚设计集团、公和设计集团、铭扬工程设计集团、地标设计集团、上宸工程设计集团、华坤建筑设计院、广厦建筑设计研究院、上海传承博华建筑规划设计院、航冠工程设计院、浙江鸿能…

控制台打印如来佛图像

代码 System.out.println(" _ooOoo_ \n"" o8888888o \n"" 88 \".\" 88 …

Java王者荣耀火柴人

主要功能 键盘W,A,S,D键&#xff1a;控制玩家上下左右移动。按钮一&#xff1a;控制英雄发射一个矩形攻击红方小兵。按钮控制英雄发射魅惑技能&#xff0c;伤害小兵并让小兵停止移动。技能三&#xff1a;攻击多个敌人并让小兵停止移动。普攻&#xff1a;对小兵造成基础伤害。小…

【人工智能 | 知识表示】问题规约法 谓词/符号逻辑,良好的知识表示是解题的关键!(笔记总结系列)

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

【算法优选】 动态规划之路径问题——贰

文章目录 &#x1f38b;前言&#x1f332;[下降最小路径和](https://leetcode.cn/problems/minimum-path-sum/)&#x1f6a9;题目描述&#x1f6a9;算法思路&#xff1a;&#x1f6a9;代码实现 &#x1f38d;[最小路径和](https://leetcode.cn/problems/minimum-path-sum/)&…

12.11

1.q&#xff0c;w&#xff0c;e亮led1&#xff0c;2&#xff0c;3&#xff1b; a&#xff0c;s&#xff0c;d灭led1&#xff0c;2&#xff0c;3&#xff1b; main.c #include "uar1.h"#include "led.h"void delay(int ms){int i,j;for(i0;i<ms;i){for…

计算机科学与技术认识实习【报告】

一、实习目的 此次认识实习主要面对计算机科学与技术专业的同学&#xff0c;了解专业在未来的发展趋势&#xff0c;通过观看公司的介绍视频和技术发展情况招聘信息后的感想和学习体会等多种方式&#xff0c;使我们了解本专业相关领域的发展现状&#xff0c;让我们在校园内课堂上…

(纯原创)基于JavaWeb的宠物领养商城(详细源码以及开发设计报告)

摘要 本宠物领养系统以MVC分层为原则&#xff0c;数据持久化使用Mybatis&#xff0c;数据库使用MySQL&#xff0c;这些技术目前相对比较成熟&#xff0c;方便系统的维护与扩展 商城系统包括了宠物领养、用户注册、用户登录、商品查询、商品添加到购物车、删除商品等几大功能…

Java LeetCode篇-二叉树经典解法(实现:判断平衡二叉树、找两个节点最近的祖先等)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 平衡二叉树 1.1 实现判断平衡二叉树的思路 1.2 代码实现判断平衡二叉树 2.0 二叉树的层序遍历 2.1 实现二叉树层序遍历的思路 2.2 代码实现二叉树层序遍历 3.0 …

【C语言】【数据结构】自定义类型:结构体

引言 这是一篇对结构体的详细介绍&#xff0c;这篇文章对结构体声明、结构体的自引用、结构体的初始化、结构体的内存分布和对齐规则、库函数offsetof、以及进行内存对齐的原因、如何修改默认对齐数、结构体传参进行介绍和说明。 ✨ 猪巴戒&#xff1a;个人主页✨ 所属专栏&am…

Linux安装Nginx并部署Vue项目

今天部署了一个Vue项目到阿里云的云服务器上&#xff0c;现记录该过程。 1. 修改Vue项目配置 我们去项目中发送axios请求的文件里更改一下后端的接口路由&#xff1a; 2. 执行命令打包 npm run build ### 或者 yarn build 打包成功之后&#xff0c;我们会看到一个dist包&a…

中文语音标注工具FunASR(语音识别)

全称 A Fundamental End-to-End Speech Recognition Toolkit&#xff08;一个语音识别工具&#xff09; 可能大家用过whisper&#xff08;openAi&#xff09;&#xff0c;它【标注英语的确很完美】&#xff0c;【但中文会出现标注错误】或搞了个没说的词替换上去&#xff0c;所…

万户协同办公平台ezoffice wpsservlet接口任意文件上传漏洞

声明 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 一、漏洞描述 万户ezOFFICE协同管理平台是一个综合信息基础应用平台&am…

Linux网络——高级IO

目录 一.五种IO模型 1.阻塞式IO 2.非阻塞式IO 3.信号驱动IO 4.多路转接IO&#xff1a; 5.异步IO 二.同步通信 vs 异步通信 三.设置非阻塞IO 1.阻塞 vs 非阻塞 2.非阻塞IO 3.实现函数SetNoBlock 四.I/O多路转接之select 1.初识select 2.select函数原型 3.socket就绪…

3.c++进阶语法函数和指针

1.函数 5&#xff1a;03 2.指针

【51单片机系列】74HC595扩展实验之使用74HC595芯片在LED点阵中显示数字

本实验实现的功能是使用74HC595芯片实在LED点阵中显示数字字符0。 要点亮多个LED灯&#xff0c;需要用到动态数码管的动态扫描原理。 首先如何点亮一行上面的多个灯或一列上面的多个灯&#xff0c;明显就是需要某行或某列有效&#xff0c;同时使多列或多行有效。比如在第一行有…

记录 | xftp远程连接两台windows

1、打开openssh 设置 -> 应用 -> 可选功能 -> 添加功能 -> OpenSSH 客户端&#xff0c;将 ssh 客户端安装将两台电脑的 ssh 开启&#xff0c;cmd 中输入 net start sshd2、配置 win10 账号密码 3、进行 xftp 连接

【Spring】@SpringBootApplication注解解析

前言&#xff1a; 当我们第一次创建一个springboot工程时&#xff0c;我们会对启动类&#xff08;xxxApplication&#xff09;有许多困惑&#xff0c;为什么只要运行启动类我们在项目中自定义的bean无需配置类配置&#xff0c;扫描就能自动注入到IOC容器中&#xff1f;为什么我…

java--LinkedList集合的底层原理

1.什么是链表&#xff1f;有啥特点&#xff1f; ①链表中的结点是独立的对象&#xff0c;在内存中是不连续的&#xff0c;每个结点包含数据值和下一个结点的地址。 ②链表的特点1&#xff1a;查询慢&#xff0c;无论查询那个数据都要从头开始找。 ③链表的特点2&#xff1a;链…