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

2023-11-04


串口的基础协议总结见: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码

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

UART串口Shell软硬件模型分析总结 的相关文章

随机推荐

  • 3个权威免费资源下载网站!

    hello大家好 这里是预计今天可以到家的老Y工作室 因为这几天在出差 也没花太多时间帮大家搜罗一些好玩有趣的网站 等回家后 会把补一些软件和教程 有朋友之前问老Y有没有免费的标准下载网站或者查询 于是老Y找了3个给有需要的朋友分享一下 0
  • Redis初级篇

    Redis 视频地址 https www bilibili com video BV1Rv41177Af p 38 资料地址 https pan baidu com s 1GxYRq5UkZHKhk3KB0nOioQ q7vj 概述 Red
  • Windows C++多线程:生产者消费者模型编程

    Windows C 多线程 生产者消费者模型编程 生产者消费者模型是一种常见的并发编程模型 用于解决生产者和消费者之间的数据交互问题 在这个模型中 生产者负责生成数据并将其放入共享的缓冲区 而消费者则从缓冲区中获取数据进行处理 在Windo
  • 软件项目管理

    一 填空题 1 项目是为创造独特的产品 服务或成果而进行的临时性的工作 2 PMBOK 2016 将项目管理分为五个过程组 即启动 计划 执行 控制和收尾 与十大知识领域 整合管理 范围管理 时间管理 成本管理 质量管理 人力资源管理 沟通
  • 如何申请国内博士

    博士申请过程还算平坦 在申请过程中得到了很多学长学姐的帮助 为了将这份帮助传递下去 我便将我的经历写下来 希望可以帮到一些同学 先介绍一下自己的情况 专业是计算机 研究方向是深度学习 机器视觉 硕士是一所排名较低的211 博士最终申请的学校
  • Lodash 总结

    数组 数组创建 随机创建数组 range range start 0 end step 1 let arr range 24 console log arr 0 1 2 3 23 创建相同元素数组 fill fill array value
  • 数据通信-路由基础

    1 IP路由选择原理 路由器的工作内容 路由器知道目标地址 发现到达目标地址的可能的路由 选择最佳路径 路由表 维护路由信息 转发IP数据 IP路由表 初始化情况下 路由器所知的网段 只有其直连接口所在网段 路由器自动将接口所在网段的路由写
  • 【注意】Kafka生产者异步发送消息仍有可能阻塞

    文章目录 问题描述 原因分析 解决办法 总结 问题描述 Kafka是常用的消息中间件 在Spring Boot项目中 使用KafkaTemplate作为生产者发送消息 有时 为了不影响主业务流程 会采用异步发送的方式 如下所示 Slf4j
  • GIS 图层

    GIS图层 从直观的角度看 就是按某种属性对数据分为若干文件 比如铁路L 公路 城市道路 乡村道路 分成若干层 从开发的角度 图层对应一张数据库表 这张表可能存在数据库中 也可能存在GIS相关文件中 图层包含要素 要素对应一条记录 做开发时
  • 丁奇mysql学习笔记-基础篇

    一 mysql的逻辑架构 问题解决 如果表 T 中没有字段 k 而你执行了这个语句 select from T where k 1 那肯定是会报 不存在这个列 的错误 Unknown column k in where clause 你觉得
  • 在Linux安装anaconda3和Pycharm

    第一步 在anaconda3官网下载Linux版本的anaconda3 官网 anaconda3 下载完成后打开终端terminal 进入下载文件的所在目录下 输入ls就可以看到所下载的文件了 第二步 准备安装Anaconda3 进入安装包
  • 【C语言】中,输入一个数组,用for循环实现将输入的n个数字按照从大到小的顺序输出。【通俗简单易懂】

    本篇文章中 我们将讲述在C语言中 输入一个数组 如何用for循环实现将输入的n个数字按照从大到小输出 一 定义数组并初始化 首先 我们定义一个整形的数组并将其初始化 输入n 来决定数组中整数的个数 然后用for循环来输入这个数组中的各个数字
  • mysql用存储过程通过传入的参数id更新字段数据,结果更新了表中所有该字段的数据

    近日一个业务 需要用存储过程实现 其中有一句只是简单的通过id更新字段的语句 但是调用存储过程中发现 会更新表格所有数据 如下图 图一 表中现有数据 需要更新的字段是红框里的 图二 存储过程截图如下 红框里的更新语句 调用存储过程 如图三
  • 二十. Kubernetes 存储挂载

    目录 一 存储挂载 volumes卷的分类 1 EmptyDir 示例 2 hostPath 示例 3 HostDir 示例 4 NFS 网络数据卷示例 持久化卷 二 持久化存储的 PV PVC StorageClass PV 的创建与使用
  • fswebcam一次拍摄多张图片(存在问题)

    初衷 由于树莓派上使用python v4l2capture有问题 因此想了个中间办法 使用fswebcam一次连续拍摄多张图片 fswebcam no banner d dev video0 r 1920x1080 F 15 save 1
  • 一文汇总开源大语言模型,人人都可以拥有自己的ChatGPT

    前言 OpenAI发布的ChatGPT火爆全球以来 全球互联网大厂陆续跟进 纷纷宣布了自家的Chat产品 如Google的Bard 百度的文心一言 阿里的通义千问等等 这些Chat产品背后都是依赖的大语言模型 Large Language
  • keepalive+haproxy实现高可用

    目录 一 搭建环境 1 基本环境 二 修改配置文件 1 建立haproxy配置文件 2 修改haproxy配置文件 3 修改keeplive配置文件 三 启动服务验证 1 HAproxy虚拟机启动haproxy服务和keepalived服务
  • 守护进程的编程规则

    要理解守护进程的编程规则必须先搞明白进程组 会话 组长进程等关系 1 进程组 每个进程除了有一个进程ID之外 还属于一个进程组 进程组是一个或者多个进程的集合 每个进程组都有一个组长进程 组长进程的标识是 其进程ID和进程组ID相等 2 会
  • 基于粒子群算法优化的DBN深度置信网络数据预测及其Matlab实现

    基于粒子群算法优化的DBN深度置信网络数据预测及其Matlab实现 深度置信网络 Deep Belief Network DBN 是一类具有多层结构的前向神经网络 它由多个受限制玻尔兹曼机 Restricted Boltzmann Mach
  • UART串口Shell软硬件模型分析总结

    文章目录 层次一 最底层逻辑配置交互 如何从Uart硬件读写单个字节数据 层次二 抽象串口软件模块交互 基于串口对接输入输出流 和 Printf适配 层次三 类似Shell封装抽象交互 基于串口交互命令行界面 命令解析 补全 修改 记录 c