本文由RT-Thread论坛账号123 原创发布:https://club.rt-thread.org/as...
RTT串口V1版本的使用分析及问题排查指南(二)
串口相关问题解析
结合串口使用过程的反馈信息,本章节将结合 FinSH组件的串口相关问题 和 应用层上使用串口设备的相关问题 这两个方面进行分析。这一章节探讨FinSH组件上的串口问题。
由于串口外设所涉及的方面过于广泛,分析时不可能涵盖全部应用场景(以上两个方面应该能涵盖了串口使用过程中的七八成的应用场景),但是,结合本章的分析问题的方法,相信其他方面的问题也将会迎刃而解。
FinSH 组件上的串口问题
说起FinSH组件,肯定是离不开串口的,FinSH组件的底层数据流默认由串口外设提供(当然也可以选择网络、USB、蓝牙等方式,本节只讨论和串口相关的问题)。
RT-Thread的FinSH组件,提供了一套供用户在命令行调用的操作接口,主要用于调试或查看系统信息,类似于Linux下的Terminal。其执行序列如下图所示(抄袭的文档中心的序列图)
结合上图,可以粗略的讲, FinSH就有点类似于在RT-Thread系统中创建了一个串口线程,它用来接收(监听)用户数据,并对数据进行解析,然后执行结果,并把需要显示的结果通过串口发送给用户。再简言之,就是串口外设的收发任务。
那么就简化成了三点:1.FinSH 线程是如何启动并工作的? 2. FinSH 是如何接收到数据的? 3. FinSH 是如何显示执行结果给用户的?
FinSH 工作流程分析
在图中最关键的就是(1)、(2)和(3)这三个关键语句。
(1)语句是设置FinSH线程的shell设备,在这里这个shell设备就是串口设备,查看它的函数原形如下:
/* finsh_set_device 的关键代码 */
void finsh_set_device(const char *device_name)
{
dev = rt_device_find(device_name);
... ...
/* open this device and set the new device in finsh shell */
if (rt_device_open(dev, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX | \
RT_DEVICE_FLAG_STREAM) == RT_EOK)
{
rt_device_set_rx_indicate(dev, finsh_rx_ind);
}
... ...
}
可以看到,该设备的打开模式是RT_DEVICE_FLAG_INT_RX
,对于发送并未指明是中断RT_DEVICE_FLAG_INT_TX
或是RT_DEVICE_FLAG_DMA_TX
,那就代表设备发送是轮询模式。也就是说,FinSH的接收是接收中断模式,FinSH的发送是轮询模式。这个很重要,一定要记住。
(2)语句是FinSH的接收函数,结合最下边finsh_getchar()
函数实体,我们可以清晰的知道,原来FinSH的数据接收是靠信号量传递的:当执行 finsh_getchar()
的时候,会去通过调用 rt_device_read()
读一个字节的数据,这个时候只要返回值不为1(返回值为1则代表有数据被读出),则将通过信号量的将该线程永久挂起 ,直到接收到FinSH数据为止。而如果有数据接收到的话,那么将通过FinSH的数据接收回调 finsh_rx_ind()
来释放一个信号量,这样 FinSH 线程就会开始执行,开始读取一个字节数据。
/* FinSH的接收回调很简单,就是触发回调后释放一个信号量就结束 */
static rt_err_t finsh_rx_ind(rt_device_t dev, rt_size_t size)
{
RT_ASSERT(shell != RT_NULL);
/* release semaphore to let finsh thread rx data */
rt_sem_release(&shell->rx_sem);
return RT_EOK;
}
(3)语句是发送语句。Finsh线程执行(2)语句之后便拿到了一个字节的数据,然后经过数据解析,就会把这一个字节数据回显到终端上,也就是把数据通过 rt_kprintf
发送到终端上。rt_kprintf
底层是如何发送串口数据的,这里由于篇幅有限,不再细说,直接说结论就是,rt_kprintf
底层对接的是串口的轮询发送模式。
FinSH总结
由上文可以得出一些结论:
- FinSH 用串口外设作数据流时,其发送模式为轮询模式,接收模式为中断模式。
- 接收靠的是信号量做数据通信,且每次进接收中断时,只能接收单字节数据。
- 接收端当没有接收到数据时,FinSH将通过信号量挂起自身,因此属于非阻塞接收模式。
- 发送端 (也包括rt_kprintf)为轮询发送,因此属于阻塞发送模式。(这里延伸一下,rt_kprintf能在中断环境下使用么?答案是可以的,因为该函数没有影响中断环境状态的变化,亦没有等待信号量或者挂起线程等操作。不过还是尽量少在中断环境下打印数据,因为中断执行时间要尽可能短,加入打印将会使得中断执行时间超过预期,从而出现不安全的意外风险)
其他问题探讨
结合FinSH的一些特性,以及日常遇到的问题,总结了一下几个频发的点以供参考:
FinSH 打开后接收不到数据:
由于FinSH是中断接收模式,既然接收不到数据,那就直接定位以下几个点:
1. `finsh_thread_entry` 线程是否初始化成功并在`finsh_getchar()`处等待? 2. 输入任意字符时,`finsh_getchar()`能否立刻返回数据? 3. 输入任意字符时,`finsh_rx_ind()`接收中断回调是否得到执行? 4. 输入任意字符时,对应的串口是否能进入串口中断,并查看串口对应的 接收数据寄存器(RDR) 是否有值(该值就是你输入的字符对应的ASCII码值)? 5. 如果连 接收数据寄存器都没有数据,那么排查串口配置的原因(包括时钟、引脚、中断等配置),或者是硬件的问题
硬件复位后系统无法输出:
其实这个问题和FinSH无关,但是很多人觉得这个是控制台无输出,应该是FinSH相关的问题 ,因此我干脆把这个问题放在这里。
这个问题和 问题1 很像,但是本质区别还是有的,就是复现的场景是复位后出现问题,那么基本可以定位到是和复位有关的。经验告诉我们,一些开发板由于考虑一键下载的功能,因此在开发板的串口上集成了
一键下载电路
,导致系统复位时直接改变了BOOT模式,从而进入串口ISP下载模式。这里给出解决办法的参考链接:硬件复位程序无法正常运行FinSH输出乱码:
出现这样的问题,要么是电脑终端控制台软件的问题,要么是波特率等配置不对,要么就是硬件没有接地。
默认FinSH输出正常,切换其他串口后就无法工作:
这个问题按照问题1去排查就行,大概率是第5点,配置有问题,串口输出重定向错误等等。
打开FinSH后系统就hardfault了:
这种问题其实没有特别的方法,就按照普通的hardfault去查就行了。也可以参照这个链接Cortex-M内核硬件故障问题的分析方法
个人调试总结
本人在调试finsh的时候,通常是采用比较直接的手段,首先关注FinSH线程是否启动,如果是正常的话就直接拉出串口的发送数据寄存器TDR和接收数据寄存器RDR(根据MCU的版本和型号不同,这两个寄存器有的统一叫做DR数据寄存器,不过功能是一样的)。个人觉得这样的方式是最有效率的,下面以STM32F4为例,以MDK调试环境进行说明。
如何判定串口FinSH接收是正常的?
上文已经总结过,FinSH的接收是中断接收,那么很简单就在串口接收中断的位置打个断点,然后键盘输入一个字符看中断是否触发,并判断接收数据寄存器是否是输入的字符对应的ASCII码值。如图所示:
左边方框断点位置就是串口接收中断触发的位置,右边DR寄存器就是串口接收到的字符存放的位置,此时让程序全速运行。当不输入字符时,这个断点处的代码是不会执行到的,现在敲击键盘一个数字 “1”,如果是正常的情况的话,将会得到如下图所示的效果:
输入数字 “1” 后,可以看到程序执行到断点的位置了,此时的DR数据寄存器也获得了数字 “1”对应的ASCII码值,这就代表FinSH接收数据是正常的。
如何判定串口FinSH发送是正常的?
按照接收的测试方法类推,发送数据时,是使用的轮询发送模式,那么直接在stm32_getc()
函数位置设置断点,如下图所示:
你可以利用rt_kprintf
,或者是FinSH的回显功能,最终都会执行到此处代码段,然后查看DR寄存器数据是否是正确的即可。
应用层的串口问题
未完待续,见下一章节。。。