单片机串口控制台方案

通过串口使用类似Linux命令的方式控制单片机,大幅提高调试效率,可以在非debug模式下实现类似debug的效果
网上找了找类似的东西,好像资料不是很多的样子,FreeRTOS提供了解决方案,RTthread也有FinSH控制台,但是感觉有点复杂,所以自己做了一个,非常轻量,快速,没有多余的功能

串口接收部分使用FreeRTOS操作系统+任务通知+DMA+空闲中断实现(参考:CH32串口接收方案),当然也可以自己使用其他的方式去实现
效果展示
在终端中输入 list,用于显示当前已注册命令

在终端中输入 info,显示当前已有任务的剩余堆栈,这对于合理分配任务大小很有帮助

大致工作流程
首先初始化控制台,主要是启动串口接收,因为控制台使用了动态单向链表,所以还需要为头节点分配内存,初始化完成后即可将自己需要的命令字和功能回调函数注册进去(命令字和功能函数是一一对应的,每个命令都对应了一个特定的功能函数),之后由串口接收任务等待上位机命令,接收到命令后根据命令去调用指定的功能函数即可

详解
一、 相关定义(.h)
console.h
首先需要一个函数指针类型,这个类型就是注册进系统的功能回调函数的类型,无返回值,有一个char* 类型的参数,可以用于接收上位机发送的指令,进行参数解析,用于实现类似于“setv -1 -200 -3000”这样的带参效果
然后定义了一个自定义的错误类型,这个可有可无
最后是链表的节点,每个节点由 一个命令字+一个功能函数+下个节点的地址 组成
二、 具体实现(.C)
console.c
1.全局变量
定义了一个任务句柄,可有可无
定义了一个链表头指针,用于查找控制整个链表
定义了一个串口接收数组,用于接收命令,空间可以给大点也可以给小一点,因为命令并不会很长,但是考虑到误操作的问题,太小了容易导致溢出
2.串口相关
第一个函数用于启动串口接收,使用DMA+串口空闲中断的方式
第二函数是接收回调函数,这个函数被放置在STM32的串口空闲事件中(
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
)其他单片机则放置在对应的中断触发处,用于通知控制台任务执行以及使能下次接收
3.初始化控制台
首先调用上面写好的函数完成串口配置,然后为之前定义的头指针分配一块内存,做一下简单的空指针判断,随后完成初始化
4.命令注册
该函数拥有两个参数,分别是指令和对应函数,首先定义一个“now”临时节点,让他等于头节点,然后判断他的 指向下个节点的 指针变量 是否为空,如果不为空则说明不是尾节点,需要判断一下当前节点的指令和参数传入的指令是否一致,如果一致则说明指令重复,退出函数即可,如果指令不一样,则让now节点等于该变量指向的节点,随后进入下一个循环判断,直到找到尾节点,找到尾节点后即可分配一块节点内存,并让尾节点指向这个节点,同时使用传入参数为该节点变量进行赋值,至此完成命令注册
5. 命令卸载
使用该函数可以实现注销一个指令的操作,但是我觉得没什么用,因为注册了命令之后基本上也不会有什么注销的需求,所以就没写(笑),如果需要的话可以自己实现,无非就是比较到相同的指令,然后释放这个节点的内存,并让上个节点指向下个节点
6. 命令调度
函数传入的是终端输入的命令字符串,首先用自制的字符串比较方法进行指令的比较判断,一个一个比较,直到发现不一样的字符,此时看看节点的命令 是否 每个字符都比较完了,通过字符串结尾的\0判断,如果当前index的位置不是\0说明还没比较结束就出现了不一样的字符,指令不匹配进入下一次循环,如果是\0则说明匹配到了一个节点,则可以调用该节点内部的功能函数,并且将上位机输入内容以参数传进,继续做参数解析(这样子的好处是只关心输入内容的前面一部分是否与指令相同,而不关心其他的部分,可以方便的做指令参数,例如注册的指令为“open”,输入的内容为“open -1 -2”,即可匹配到open命令,并将整个字符串传入到功能函数中,在函数内解析出剩下的参数)
7. 命令展示
一个功能函数,用于打印出当前已注册的命令有哪些

终端介绍
在开始下一节之前先介绍一个概念,终端的输入方式和串口调试助手的输入方式是不一样的,在串口调试助手中会先输完完整的信息,然后点击发送,单片机一次性收到一整条消息,并且也能看到自己到底写了什么,如下图

终端则截然不同,首先终端的输入是一个字一个字发送的,假设在终端中键盘按下了一个h,此时这个h就已经发送给单片机了,并且终端上并不会显示h,因为终端只管发送,要想看到自己写了什么内容,需要单片机接收到字符后再发送回终端,以此才能实现类似于Linux终端那样的效果
我使用的终端是MobaXterm,不用付费也能用,为什么要用终端不用串口助手呢,因为终端可以进行颜色的解析,大部分串口助手只能显示黑色的字,无法解析颜色

8. 控制台任务
首先定义一个临时数组,因为终端发送数据是一个一个发的,所以不能以一帧做为结束,在判断回车结束之前需要存放在这个缓存区
将 查看已存在命令 的函数注册到控制台中,这样就可以在控制台查看已有命令了,将该函数与“list”命令绑定
使用任务等待,阻塞在这里,直到收到数据为止
收到消息后就将数据存入缓冲区,同时记录当前数据的累计大小
判断一下当前数据的结尾是不是\r或者\n,如果是的话则说明一帧结束,同时判断一下数据大小,如果发现没有数据,只是在终端单纯敲了个回车的话,则打印一个>>,看起来更有感觉,如果有数据则使用cs_fun_callback()函数进行命令分发

终端里输入命令的时候可能会输错,所以需要实现退格删除功能,单片机检测退格,如果发现输入了退格,先把缓冲区的下标记录变量-2,为什么是-2不是-1,因为首先要减掉当前输入的退格键,然后再减掉真正输错的那个字,同时在控制台先打印一个退格,让光标往前移动一格,再打印一个空格,遮盖掉打错的字,然后再发送一个退格,退回到即将输入的位置
如果都不是上述的情况,则说明只是普通的输入了一个字符,将这个字符通过终端打印出来,实现“回显”的效果
当然最重要的,要创建一个线程,让他跑起来
9. 功能函数示例
该函数实现了控制 某个电机 以 指定速度 转动 多少圈,传入参数由命令分发函数传入,内容就是输入的串口内容

用字符串切割函数即可取出所需的参数,通过函数转换为自己想要的类型后即可使用
结束
至此内容结束,完成了完整的控制台功能,可以自定义任意多的命令,并在单片机运行的时候随时使用,比如需要检测任务剩余堆栈,以前需要一直打印信息,导致串口上有一大堆无用数据,淹没了真正重要的有价值的数据,现在只需要将打印内存信息的功能封装成一个函数,注册一个命令,即可在适当的时候人为控制打印一次,方便简洁
还可以注册一些可能需要灵活更改、变化的功能,比如以前想要改某个参数或功能需要在代码当中更改,然后重新编译下载,但是现在只需要提前做好功能函数,即可通过控制台随时更改,极大的方便了测试
总之好处多多,该方案为本人原创,欢迎提出意见૮(˶ᵔ ᵕ ᵔ˶)ა
感谢阅读(●'◡'●)
