UART串口Shell软硬件模型分析总结

news2025/1/19 20:33:03

文章目录

    • 层次一、最底层逻辑配置交互----如何从Uart硬件读写单个字节数据
    • 层次二、抽象串口软件模块交互----基于串口对接输入输出流 和 Printf适配
    • 层次三、类似Shell封装抽象交互----基于串口交互命令行界面(命令解析、补全、修改、记录)
      • case1 依次输入字符abc回车----可见字符回显,回车解析,行首提示符
      • case2 输入abc左移动两次光标删除a插入A----移动光标,指定位置删除与插入
      • case3 输入多条命令后 按上下按键----查看历史命令
      • case4 输入部分字符后按TAB按键----自动补全命令
      • case5 忽略CTRL+A\CTRL+C等特殊控制命令

串口的基础协议总结见:UART串口协议简单总结 https://blog.csdn.net/runafterhit/article/details/114990221
本文用于UART串口协议软硬件模型分析,不再讲解基础协议,目的讲解清楚 从逻辑模型 到 软件Shell设计 的交互过程。
整个过程以类似伪代码的方式 目的是描述主要细节,能帮助理解整个交互中的设计点;

层次一、最底层逻辑配置交互----如何从Uart硬件读写单个字节数据

串口的基本协议如下,不做过多描述;
在这里插入图片描述
第一个层次,我们看一下Uart基本硬件逻辑如何交互。大部分Uart的硬件寄存器模型都可以抽象到下面内容:
1)、初始化与使能硬件:包括速率配置、数据模式配置(bit位宽、校验方式、校验使能);
2)、数据的发送:发送数据Tx FIFO寄存器(有一定深度)+ FIFO是否满 状态寄存器;
3)、数据的接受:读取数据Rx FiFo寄存器(有一定深度)+ FIFO是否空闲 状态 寄存器;
4)、串口中断的控制:中断mask/status/clear;
5)、串口DMA控制:DMA相关使能 和 控制;
本文讨论的串口模块 没有使用中断和DMA,串口的速率较低常见115200bps,在大部分简单嵌入式设备中 都没有必要使用中断,用轮询的方式足够。DMA类似,如果用Uart作为 命令行交互 通常不需要用DMA,当用Uart做大量数据传输时使用;

那么第一个层次 Uart硬件的寄存器配置抽象大致如下:

void UartHardWareInit(u32 clk)
    配置Uart速率寄存器
    配置Uart数据模式寄存器
    配置Uart使能寄存器

void UartHardWarePutc(char c)
    while 循环检查Tx FiFo状态寄存器 是否 未满;
    (空闲后)往Tx FiFo写入c

int UartHardWareGetc(void)
    检查Rx FiFo状态寄存器 是否 空闲
        若 空闲 return 返回0
        若 非空 return 读取Rx FiFo字符

bool UartHardWareTstc(void)
    return Rx FiFo状态寄存器 是否 空闲

层次二、抽象串口软件模块交互----基于串口对接输入输出流 和 Printf适配

第二个层次,我们需要抽象一个软件接口,能通过读取字符 和 串口输出字符串 的基本接口;
这个层次的读取字符基本就是透传逻辑接口,在发送一个字符时要考虑平台要求 比如 windows平台要求,当换行时需要补充’\r’
适配Printf打印函数,主要是要先把 变长参数 转化为固定字符串,再通过串口输出字符串即可;
当前这个层次还不涉及复杂的软件逻辑;

//读取一个字符
int UartGetc(void)
    return UartHardWareGetc();

//输出一个字符
void UartPutc(char c)
    // windows平台要求,当换行时需要补充'\r'
    if (c == '\n')
        UartHardWarePutc('\r')
    UartHardWarePutc(c)

//输出字固定符串
void UartPuts(char *s)
    while(*s) { UartPutc(*s++);}

//适配基础打印printf
void Printf(char *fmt, ...)
   // 先把变长参数 转换定长字符串
   va_start(ap,fmt);
   vsprintf(str,fmt,ap);
   va_end(ap)
   // 调用串口输出字符串
   UartPuts(str);

层次三、类似Shell封装抽象交互----基于串口交互命令行界面(命令解析、补全、修改、记录)

第三个层次,业务逻辑就变得复杂了。类似Shell的基本命令行交互界面,我们先考虑下面几个基本行为;
1)行首提示符,同步回显输入可见字符,回车解析命令:在串口界面 行首带有命令提示符 比如【$】或者【#】,输入可见字符要同步显示到终端界面上,回车后整个字符串 作为命令设备进行解析;
2)可调整光标位置,插入删除字符 :字符串在回车前,可以通过左右方向按键 移动光标位置,在位置处插入字符 或 删字符;
3)可查看历史命令:在终端按上下方向按键时,能查看历史命令并显示到终端,找到后按回车可以直接输入;
4)可用TAB命令补齐:在终端输入部分字符时,通过TAB按键能进行命令补齐,匹配单个时直接补全,多个时显示出来;
5)忽略CTRL+A\CTRL+C等特殊控制命令 部分终端串口界面上经常使用的操作命令,设备需要进行主动忽略;
整个数据链路的交互如下,一定要区分好终端的显示界面 输入输出的概念,当你敲入一个字符后本身不会在终端显示出来,终端的显示内容全部都来自于 嵌入式设备 中Shell交互模型来定义,根据受到的字符 判断 如何显示到终端界面上,通过Tx发送到终端显示出来;
在这里插入图片描述

嵌入式设备Shell软件模型,大致我们可以梳理为类似下面的逻辑:

// 解析流程,ms级别的循环调度即可
void ShellCmdProcess(void)
    UartGetc() 获取字符,失败return -1
        判断是否是方向按键,需要累计3字符状态
            中间的过程字符转化忽略按键
            3字符累计结果转化为 方向动作 操作符
                上下按键操作---历史命令显示 return
                左右按键操作---光标左右处理 return
        判断是否是忽略按键,如CTRL A等,丢弃不做处理 return
        判断是否是删除按键,进行位置删除操作(注意非尾部状态) return
        判断是否是TAB按键,进行命令补全,return
        判断是否为ENTER回车按键
        	命令记录,匹配系统命令,查找是否存在 进行回调响应 return
        判断是否为特殊响应功能键,进行响应,return
        中间输入的普通字符累计(注意非尾部状态)
        	普通字符回显到终端,累计记录到输入命令串中       

下面我们针对这几个case提出一些软件交互行为设计模型,配合ASCII码表更好理解下面描述;
在这里插入图片描述

case1 依次输入字符abc回车----可见字符回显,回车解析,行首提示符

界面行为:依次输入abc,输入每个字符过程回显到终端,回车后换行,行首出现提示符$
RX输入:0x61 0x62 0x63 0x0D
—0x61~0x63依次是abc字符的ASCII,0x0D为回车
Tx输出: 0x61 0x62 0x63 0x0D 0x0D 0x0A 0x24 0x20
—0x61~0x63依次是abc字符的ASCII,0x0D回车0x0A换行0x24为行首$提示符号,0x20为空格;

void ShellCmdProcess(void)
    UartGetc() 获取字符,失败return -1
        判断是否是方向按键...
        判断是否是忽略按键...
        判断是否是删除按键...
        判断是否是TAB按键...
        判断是否为ENTER回车按键
            // 3、当识别到ENTER按键时,把累计命令cmd串进行匹配响应
        	命令字符串记录(用于上下按键查看历史命令,后面讲解)
            按记录命令cmd串 匹配查看是否存在内部命令 进行回调响应
            // 4、让终端显示进行回车换行,并显示行首的提示符
            输出换行和行首的命令提示符号UartPuts("\r\n$ ");
        判断是否为特殊响应功能键...
        中间输入的普通字符累计(注意非尾部状态)
            // 1、每个非控制类的普通显示字符都需要 回显到终端
            普通字符回显到终端UartPutc(c)
            // 2、同时累计到记录的命令cmd串中
            累计记录到输入命令串中(注意中间插入场景,在后面方向按键讲解)

在这里插入图片描述

case2 输入abc左移动两次光标删除a插入A----移动光标,指定位置删除与插入

备注:上下左右方向按键 一个按键由三个ASCII组成,0x1b+0x5b+0x41上/ 0x42下/ 0x44/ 0x43右
界面行为:依次输入abc,然后按两次左方向按键 光标移动到a,回删按键删除a(显示bc),再输入A字符(显示Abc),
RX输入:0x61 0x62 0x63 0x1b 0x5b 0x44 0x1b 0x5b 0x44 0x08 0x41
—0x61~0x63依次是abc字符的ASCII,0x1b 0x5b 0x44 三个字符组合为左方向按键,0x08回退, 0x4a为A的ASCII
Tx输出: 0x61 0x62 0x63 0x08 0x08 0x08 0x62 0x63 0x20 0x08 0x08 0x08 0x41 0x62 0x63 0x08 0x08 0x08
—0x61~0x63依次是abc字符的ASCII,两次0x08是两次左移光标;0x08 0x62 0x63 0x20 0x08 0x08 0x08 行为是先左移动到a字符位置,重新输出b c [空格] 就把abc替换为bc[空格]达到删除字符效果,然就再0x8回退三次就返回到原来首行的光标位置;0x41 0x62 0x63 0x08 0x08 0x08 行为是重新输出A b c就把bc替换为Abc达到插入A字符效果,然后再回退到初始位置;

核心思路:shell软件用标志pos记录 始终匹配终端光标位置,len记录已输入的cmd累计长度;
左移:往终端输出 回退0x8控制光标左移,刷新pos;右移:往终端输出cmd[pos],通过输出相同字符让光标右移,刷新pos;
非尾部插入:先往终端输出插入字符,然后把之前cmd[pos]开始子串再输出一遍,就把终端从pos处更新出新字符串,之后再连续回退调整光标位置;
非尾部删除:先往终端输出回退0x8,把cmd[pos]之后字串输出一遍,再通过输出空格替换原来终端显示尾部字符,之后再连续回退调整光标位置;

void ShellCmdProcess(void)
    UartGetc() 获取字符,失败return -1
        判断是否是方向按键,需要累计3字符状态
            // 1、当识别到0x1b字符时 开始匹配后续字符
            中间的过程字符转化忽略按键,不进行回显示和统计到cmd串
            // 2、根据方向字符最后一个0x41上/ 0x42下/ 0x44/ 0x43右,得到方向操作
            3字符累计结果转化为 方向动作 操作符
                上下按键操作...
                左右按键操作---光标左右处理
                    // 3、左按键:(当光标pos未在最左)若往左就往终端输出0x8回退光标,更新pos
                    // 右按键:(当光标pos未在最右len) 就往终端再输出一次本身cmd位置字符,更新pos
                    左按键:if (pos > 0) { UartPutc(0x8);pos--; }
                    右按键:if (pos < len) { UartPutc(cmd[pos]);pos++; }
        判断是否是忽略按键...
        判断是否是删除按键...
                    // 4、删除时如果在尾部,就回退一格,然后输出空格,在回退,更新pos和len
                    尾部:if (pos == len) { UartPutc(0x8);UartPutc(0x20);UartPutc(0x8);
                        cmd[pos] = '\0';pos--;len--} 
                    // 删除时如果非尾部,就要先回退一格,然后重新输出后续字符串+空格替换,最后再回退到初始位置
                    非尾部:UartPutc(0x8);UartPutns(cmd[pos],len-pos));UartPutc(0x20);多次回退 循环UartPutc(0x8);
                        memmove(&cmd[pos-1], &cmd[pos], len-pos);cmd[pos] = '\0';len--;pos--;//更新cmd字符串;    
        判断是否是TAB按键...
        判断是否为ENTER回车按键...
        判断是否为特殊响应功能键...
        中间输入的普通字符累计(注意非尾部状态)
            // 1、当插入位置不是在尾部时,需要先输出插入字符,并把原来pos开始的cmd字符到尾部再输出一遍;
             memmove(&cmd[pos+1], &cmd[pos], len-pos+1);len++;cmd[pos]=c;
             UartPuts(cmd[pos],len-pos);pos++;多次回退 循环UartPutc(0x8);

case3 输入多条命令后 按上下按键----查看历史命令

通过上下按键 查看历史命令的思路如下,每次输入ENTER按键时,把CMD字串记录到历史list中标识index。当按上下按键时先 从Tx输出 让终端的显示行清空,并把对应的list中index记录输出到终端,同时把当前运行的cmd更新为list中对应的cmd记录。

void ShellCmdProcess(void)
    UartGetc() 获取字符,失败return -1
        判断是否是方向按键
            中间的过程字符转化忽略按键,不进行回显示和统计到cmd串
            3字符累计结果转化为 方向动作 操作符
            // 2、上下按键操作
                上下按键:依次回删本行全部输入,然后把record中对应index中cmd找出
                把cmd[index]输出到终端,并更新到当前输入的cmd中,更新pos和len
        判断是否是忽略按键...
        判断是否是删除按键...
        判断是否是TAB按键...
        判断是否为ENTER回车按键
            // 1、当识别到ENTER按键时,先把命令记录到历史数组
        	命令记录record(cmd),记录到历史命令列表中
            匹配系统命令,查找是否存在 进行回调响应
        判断是否为特殊响应功能键...
        中间输入的普通字符累计
        	普通字符回显到终端,累计记录到输入命令串中 

case4 输入部分字符后按TAB按键----自动补全命令

自动补全的策略如下,当识别到TAB按键的ASCII后,把已经输入的cmd串 和 系统中的命令合集进行匹配。
1)如果没有找到,不做补全操作,运行cmd和终端显示不变;
2)如果找到 并且 只有一个,直接补全为对应的命令串,更新cmd并刷新终端显示完整的命令串;
3)如果找到 发现不止一个,就先换行,然后挨个打印出匹配命令字符串,再换行并输出一遍当前cmd串;

void ShellCmdProcess(void)
    UartGetc() 获取字符,失败return -1
        判断是否是方向按键...
        判断是否是忽略按键...
        判断是否是删除按键...
        判断是否是TAB按键
            把当前输入的cmd字符串 和 系统支持的命令字串进行对比,记录是否找到,找到个数
                如果未找到,不做补全
                如果找到,并且只有一个,直接补全到当前cmd,并输出到终端显示刷新;
                如果找到,并不止一个,就先换行,然后挨个打印出匹配命令字符串,再换行并输出一遍当前cmd串
        判断是否为ENTER回车按键...
        判断是否为特殊响应功能键...
        中间输入的普通字符累计
        	普通字符回显到终端,累计记录到输入命令串中 

case5 忽略CTRL+A\CTRL+C等特殊控制命令

要做到忽略CTRL+A\CTRL+C等特殊控制命令,只需要在解析字符前,剔除掉即可。
#define CTRL_© (© - ‘a’ + 1) // CTRL加字符时 受到的ASCII码

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

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

相关文章

自建音乐服务器Navidrome之一

这里写自定义目录标题 1.1 官方网站 2. Navidrome 简介2.1 简介2.2 特性 3. 准备工作4. 视频教程5. 界面演示5.1 初始化页5.2 专辑页 前言 之前给大家介绍过 Koel 音频流服务&#xff0c;就是为了解决大家的这个问题&#xff1a;下载下来的音乐&#xff0c;只能在本机欣赏&…

上海的正西边有哪些城市

背景 上海一路向西&#xff0c;来一趟拉萨之行&#xff0c;那么上海出现&#xff0c;所经过的那么多城市&#xff0c;哪些是在上海的正西边呢&#xff1f; 画一幅地图 基于这个背景需求&#xff0c;我们需要拿来一幅地图&#xff0c;一看便知。下面的python代码生成了一幅地…

通信原理板块——平稳随机过程

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 1、平稳随机过程的定义 (1)严平稳随…

UE4 显示遮挡物体

SceneDepth是你相机能够看见的物体的深度距离 CustomDepth是你相机包括看不见被遮挡的物体的深度距离 如果CustemDepth比SceneDepth的距离相等&#xff0c;那么就是没有被遮挡的物体&#xff0c;如果被遮挡那么就是CustemDepth比SceneDepth深度距离远&#xff0c;然后再做对应…

PYTHON知识点学习-循环语句

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是Aileen★。希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f194;本文由 Aileen_0v0★ 原创 CSDN首发&#x1f412; 如需转载还…

【机器学习】线性回归

Model Representation 1、问题描述2、表示说明3、数据绘图4、模型函数5、预测总结附录 1、问题描述 一套 1000 平方英尺 (sqft) 的房屋售价为300,000美元&#xff0c;一套 2000 平方英尺的房屋售价为500,000美元。这两点将构成我们的数据或训练集。面积单位为 1000 平方英尺&a…

Swift 如何从图片数据(Data)检测原图片类型?

功能需求 如果我们之前把图片对应的数据(Data)保持在内存或数据库中,那么怎么从 Data 对象检测出原来图片的类型呢? 如上图所示:我们将 11 张不同类型的图片转换为 Data 数据,然后从 Data 对象正确检测出了原图片类型。 目前,我们的代码可以检测出 jpeg(jpg), tiff,…

WebRTC 安全之一

WebRTC 的安全需要满足三个基本需求 Authentication 用户访问需要认证Authorization 用户访问需要授权Audit 用户的访问应该可被追踪和审查 其中前两项也可以归结为 CIA Confidentiality 机密性&#xff1a;信息需要保密&#xff0c; 访问权限也需要控制Integrity 完整性&#…

Spring Cloud集成Nacos配置中心/注册中心

Spring Cloud版本 2021.0.5 Spring Cloud Alibaba版本 2021.0.5.0 Spring Boot版本 2.7.10 pom文件 需要放在依赖管理的pom文件 <dependencyManagement><dependencies><!-- spring boot依赖 --><dependency><groupId>org.springframewor…

2023-9-3 试除法判定质数

题目链接&#xff1a;试除法判定质数 #include <iostream>using namespace std;bool is_prime(int n) {if(n < 2) return false;for(int i 2; i < n / i; i){if(n % i 0) return false;}return true; }int main() {int n;cin >> n;while(n--){int x;cin &g…

git大文件推送报错

报错信息 不多掰扯&#xff0c;直接上报错信息和截图 Delta compression using up to 8 threadsRPC failde; HTTP 413 curl 22 The requested URL returned error: 413 Request Entity Too Large从以上的报错信息不难看出推送仓库的时候&#xff0c;请求体过大&#xff0c;为…

C++ do...while 循环

不像 for 和 while 循环&#xff0c;它们是在循环头部测试循环条件。do…while 循环是在循环的尾部检查它的条件。 do…while 循环与 while 循环类似&#xff0c;但是 do…while 循环会确保至少执行一次循环。 语法 C 中 do…while 循环的语法&#xff1a; do {statement(s…

AD16 基础应用技巧(一些 “偏好“ 设置)

1. 修改铺铜后自动更新铺铜 AD16 铺铜 复制 自动变形 偏好设置 将【DXP】中的【参数选择】。 将【PCB Editor】中的【General】&#xff0c;然后勾选上【Repour Polygons After Modification】。 2. PCB直角走线处理与T型滴泪 一些没用的AD技巧——AD PCB直角走线处理与…

iOS练手项目知识点汇总

基础理解篇 Objective-C是一种面向对象的编程语言&#xff0c;它支持元编程。元编程是指编写程序来生成或操纵其他程序的技术。 Objective-C中&#xff0c;元编程可以使用Objective-C的动态特性来实现。例如可以使用Objective-C的运行时函数来动态地创建类、添加属性和方法等等…

给视频添加背景图片,让它们更具魅力!

想要让你的视频更加出彩吗&#xff1f;给它们添加背景图片是不错的选择&#xff01;但是&#xff0c;如何做到呢&#xff1f;不用担心&#xff0c;我们的视频剪辑高手可以帮助你轻松实现&#xff01;我们提供多种背景图片选择&#xff0c;你可以根据自己的喜好和需求进行选择。…

程序员自由创业周记#7:仲裁

没想到 没想到写的周记会有这么多人看&#xff0c;还能收到这么多陌生(或熟悉)朋友的真诚建议、鼓励、甚至是打赏&#xff0c;几乎所有的评论和私信我都认真的回复了&#xff0c;本想的是通过网友和朋友的监督坚定我创业的信念&#xff0c;有点外界压力也能迫使自己持续输出一…

P1886 滑动窗口 /【模板】(双端队列)+双端队列用法

例题 有一个长为 n 的序列 a&#xff0c;以及一个大小为 k 的窗口。现在这个从左边开始向右滑动&#xff0c;每次滑动一个单位&#xff0c;求出每次滑动后窗口中的最大值和最小值。 例如&#xff1a; The array is [1,3,−1,−3,5,3,6,7],and k3。 输入格式 输入一共有两行…

4. 虚拟机栈

4.1. 虚拟机栈概述 4.1.1. 虚拟机栈出现的背景 由于跨平台性的设计&#xff0c;Java的指令都是根据栈来设计的。不同平台CPU架构不同&#xff0c;所以不能设计为基于寄存器的。 优点是跨平台&#xff0c;指令集小&#xff0c;编译器容易实现&#xff0c;缺点是性能下降&…

WideDeep模型介绍

文章目录 1. Wide&Deep模型的记忆能力和泛化能力2. Wide&Deep模型的结构3. Wide&Deep模型的进化——Deep&Cross模型4. Wide&Deep模型的影响力 Wide&Deep模型是 记忆能力和 泛化能力的综合&#xff0c;是谷歌在2016年提出的。正如其名&#xff0c;Wid…

java运行程序流程

java运行程序流程 检查JDK环境 java -version 新建Java文件&#xff08;源文件&#xff09;Hello.java 打开记事本&#xff0c;输入 public class Hello {public static void main(String[] args) {System.out.println("Hello");} } 保存文件&#xff0c;把文件后缀…