TIPS: 米醋工作室学习笔记,本博客仅学习交流,不提供相关资源
Date:2025.1.24
Shell的组成部分
串口上位机:
普通模式
终端模式
Shell实现的功能:
输入回显功能:在终端中输入(发送)什么字符,就要在终端中显示对应的字符,本质上是单片机对接收到字符的储存。
命令功能判断功能:
单片机什么时候执行命令判断?
- 当终端按下”回车或换行键“,对应的字符为KaTeX parse error: Expected group as argument to '\r' at end of input: \r和KaTeX parse error: Undefined control sequence: \n at position 1: \̲n̲
PS:对命令行判断的过程需要在字符串数组末尾加上KaTeX parse error: Undefined control sequence: \0 at position 1: \̲0̲字符,C语言中的字符串为\0结尾,这样做保证了字符串的标准性
函数指针的设计方法:因为我们通常情况下需要定义比较多的命令,如果一个一个去写,代码就会显得比较臃肿,可以通过定义函数指针和函数名的结构体,提高程序的接口化和可拓展性。
退格键:
- 按下Backspace键,主机给单片机发送字符\b
- 单片机回传 ‘\b’ ‘空格’ ‘\b’ 三个字符。
- \b:假设当前终端上显示字符串
hello
,光标从o
之后移动到o
之前。- 空格:空格覆盖原来的
o
,此时光标向右移动一位。- \b:光标左移一位,这样,字符串显示
hell
,并且光标刚好在末尾的l
之后。TAB键(自动补全):
输入TAB,Shell能够自动关联命令,并给出提示。
第一次按下:轮询匹配
- 对终端显示的字符串进行判断,查询匹配项
- 如果有匹配项,则默认选择第一个命令,并向终端打印所有命令。
第二次按下:进行匹配
历史命令:
- 上键:
- 下键:
PS:使用指针数组,减小访存开销。
开发过程问题汇总
1. 调用自定义串口重定向函数(my_printf),在任务较多时,单片机发生死机/卡顿
问题原因:
我们首先看一下my_printf这个函数的内容
int my_printf(UART_HandleTypeDef *huart, const char *format, ...) { char buffer[512]; va_list arg; int len; va_start(arg, format); len = vsnprintf(buffer, sizeof(buffer), format, arg); va_end(arg); HAL_UART_Transmit(huart, (uint8_t *)buffer, (uint16_t)len, 0xFF); return len; }
其中
HAL_UART_Transmit(huart, (uint8_t *)buffer, (uint16_t)len, 0xFF);
:
HAL_UART_Transmit()
是 STM32 HAL 库中用于串口发送数据的函数。它用于通过 UART 发送指定长度的数据缓冲区内容。HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
函数参数解释:
UART_HandleTypeDef \*huart
:
- 类型:指向
UART_HandleTypeDef
类型的指针。- 作用:该参数是用于指定所使用的 UART 句柄,也就是你配置并初始化的串口。通过该句柄,函数知道要使用哪个串口进行数据传输。
uint8_t \*pData
:
- 类型:指向
uint8_t
类型的指针。- 作用:这是一个指向数据缓冲区的指针,数据缓冲区包含要通过 UART 发送的数据。你将要发送的内容需要通过这个指针传递。
uint16_t Size
:
- 类型:
uint16_t
。- 作用:要发送的数据的大小,以字节为单位。
Size
表示你想发送的数据缓冲区的长度。uint32_t Timeout
:
- 类型:
uint32_t
。- 作用:指定一个超时时间(以毫秒为单位)。如果 UART 发送数据需要超过这个时间,则会触发超时错误。
0xFF
是一个特殊的超时时间值,通常表示“没有超时”或“无限等待”。因此,在你的例子中,Timeout
被设置为0xFF
,表示该函数调用将一直等待,直到数据传输完成为止,不会因超时而返回。返回值:
HAL_OK
:函数成功执行,数据发送完成。HAL_ERROR
:函数执行失败,可能是由于硬件问题或其他错误。HAL_BUSY
:串口忙,当前无法执行此操作。HAL_TIMEOUT
:数据传输超时。解释:
在你的代码中:
HAL_UART_Transmit(huart, (uint8_t *)buffer, (uint16_t)len, 0xFF);
huart
:是一个指向UART_HandleTypeDef
的指针,它表示 UART 设备的句柄,通常是在初始化时配置的。(uint8_t \*)buffer
:这是一个指向要发送数据的缓冲区的指针,数据会从这个缓冲区中取出并通过 UART 发送。(uint16_t)len
:这是一个uint16_t
类型的参数,表示要发送的字节数。通常是缓冲区的长度。0xFF
:表示无限超时,函数将一直阻塞,直到数据传输完成。总结:
HAL_UART_Transmit()
函数会阻塞当前线程(直到传输完成),通过指定的huart
对象发送len
字节的数据,数据存储在buffer
中。如果你希望函数在传输过程中有超时控制,可以设置合适的超时时间。如果使用0xFF
(即无限超时),则函数会一直等待,直到数据传输完成。根据现象分析,
my_printf
使用的是串口直接发送的原理,会直接阻塞程序,且全程CPU参与,对CPU资源的消耗较大。我们可以利用DMA+串口发送重定义的方法,优化这个函数。
在定义函数前,不要忘记在CubeMX中更新配置
volatile uint8_t dmaTxCompleteFlag = 1; // DMA 发送完成标志,初始值一定要为1 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { dmaTxCompleteFlag = 1; // 设置标志,表示 DMA 发送完成 } /** @brief 串口打印(DMA)函数 * @param huart 串口发送句柄 * @param format 方法与printf一致 * @note 为什么要加标志位判断? * DMA发送和CPU是异步的。 * 假设你调用了 my_printf 第一次,开始 DMA 发送数据。此时 DMA 在后台进行传输, * 而 CPU 继续执行后续的代码。没有标志位检查时,如果立即调用 my_printf 第二次, * 第二次的打印数据可能会直接覆盖在正在进行中的 DMA 传输上,导致第二次数据无法正确发送。 * * @note 为什么用while等待不用if判断? * 举个例子,如果你用if判断发送标志位,1s/次调用此函数发送,没有任何问题 * 如果你连续两句my_printf呢?这时很可能在执行玩第一句,dma还没那么快处理完,标志位没有被重置1,第二句执行是没有效果的。 * dma因为是和cpu并行,很快就能查询到标志位,这个while其实阻塞时间极短。 * **/ int my_printf(UART_HandleTypeDef *huart, const char *format, ...) { static char buffer[512]; va_list arg; int len; while (dmaTxCompleteFlag == 0);// 如果 DMA 还未完成,等待 DMA 完成 va_start(arg, format); len = vsnprintf(buffer, sizeof(buffer), format, arg); va_end(arg); // 清除标志,表示正在开始新的传输 dmaTxCompleteFlag = 0; // 使用 DMA 发送数据 if (HAL_UART_Transmit_DMA(huart, (uint8_t *)buffer, (uint16_t)len) != HAL_OK) { return -1; // 发送失败 } return len; }
上面是最终完成版本的代码,在此之前,尝试过未完善的DMA打印函数,如果我们不关心发送中断是否完成,直接使用
HAL_UART_Transmit_DMA
,这个时候和之前使用HAL_UART_Transmit
完全不同,因为使用DMA发送时,DMA和CPU是并行执行的,DMA和CPU也可以看成异步的,HAL_UART_Transmit_DMA
不会像HAL_UART_Transmit
一样阻塞等待完整的串口发送完成,它只负责清空标志位,将数据送给DMA就可以了,所以很可能会出现:DMA还没来得及处理完数,这边又调用HAL_UART_Transmit_DMA
,覆盖了上一次DMA的数据,这样的话,会出现发送不完整甚至乱码的情况。如图所示:
所以我们需要对串口发送中断进行检查,进行DMA和CPU的同步机制。即只有DMA发送完成时,我们手动置一个标志位,这个时候才允许我们自定义的打印函数进行打印。DMA相当于承但了数据搬运的角色。4
修改为DMA串口发送后的结果:
- Shell运行流畅,且lcd和adc的任务不受影响