平时工作中程序员在调试时总会用到串口打印数据以及一些标志位查看程序是否出现问题。但是在使用时总会遇到各种各样的问题,最常见的就是输出乱码问题(指的是有收到数据但数据显示的是一堆不认识的汉字或字符),下面就我遇到过的串口乱码问题的相关几种解决方法:
方法一:检查GND线连接情况
电子系统接地非常重要,接地不当往往导致电子系统不能稳定工作。
在串口通信时地线是必须接的,比如串行数据通信接口标准(RS—232)的3线TX、RX、GND。虽然在使用RS-485总线工业标准时接两线TX、RX也能实现通信,但接Gnd 有利抑制干扰。RS-485的前身RS—422也一样。
一般建议在使用串口通信时需要把GND连接上,尤其是在长距离传输时。
方法二:查看串口助手和源程序就传输协议设置是否一致
串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。在使用串口时这些参数必须要保持一致。下面大致介绍一些这些参数的意义:
一、波特率
波特率(bandrate)是衡量符号传输速率的参数,指的是串口通信的速率,也就是串口通信时每秒钟可以传输多少个二进制位。比如串口常用波特率9600指的是串口每秒钟可以传输9600个二进制(传输一个二进制位需要的时间是1/9600秒,也就是104us)。
注意波特率和比特率的区别:
情形一:一个信号码元有且仅有两种状态:0或1
此时每种状态含一位二进制数(0或1),在这种情况下比特率值=波特率值
情形二:一个码元有4种不同的状态:01、11、00或10
此时每种状态含两位二进制数(00、01、10、11),在这种情况下比特率值=波特率值×2
以此类推可以得到:比特率=波特率 * 单个调制状态对应的二进制位数
二、数据位
数据位是衡量通信中实际数据的参数。当计算机发送一个信息包,实际的数据不一定是8位的(标准的值是6、7和8位),如何设置取决于你想传送的信息。
比如,标准的ASCII码是0~127(7位),扩展的ASCII码是0~255(8位)。如果数据使用标准 ASCII码,那么每个数据包使用7位数据;如果数据使用扩展 ASCII码,那么每个数据包使用8位数据(每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位)。
三、停止位
停止位是用于表示单个包的最后一位。典型的值为1,1.5和2位。
由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也相应越慢。
四、奇偶校验位
奇偶校验位是串口通信中在数据位后面加一位用于简单地检查数据发送是否有错。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。
对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。
例如,如果传输的数据是011,那么对于偶校验(校验位为0),则此时数据位 + 校验位的数据为:0110,保证逻辑高的位数是偶数个。如果是奇校验(校验位为1),则此时数据位 + 校验位的数据为:0110,这样就有3个逻辑高位。高位和低位不真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。
在实际使用中要确保这四个参数在源程序和助手设置是一致的。
方法三:在源程序中查看主频设置是否有误
上面两个是一般会出现乱码的原因,除此之外在源程序里面也会出现一些大意操作导致串口乱码(最难且不容易想到的出错点)。我目前尚未遇到过这种情况导致串口乱码的但在不少大佬的博客和文章中看见有遇到过,这里也引用下来大家可以看看是否能解决你的串口乱码问题。
STM32F407系统时钟配置不准确导致串口发送数据乱码、定时器定时不准问题。
https://blog.csdn.net/lqj11/article/details/108058008
stm32f407等芯片(HAL库)时钟频率修改(乱码)
方法四:Printf重定向函数
关于重定向导致数据输出乱码我遇到过几次,关于重定向引用一篇大佬的文章printf重定向原文链接:https://blog.csdn.net/RONG_YAO/article/details/115746940。这能解决大家关于重定向的一些疑惑。但我遇到过另外一种重定向导致输出乱码的情况。
在调试一个项目时一直出现乱码问题,虽然能接到数据但一直显示“IIIIIIIt”,看着不像乱码但与实际要显示的内容天差地别(不管输出的是汉字还是ASCII都一样),前面几个解决方法都检查过没有问题,但乱码现象依然没有改变。后面仔细研究源代码发现问题出在printf重定向上,不是没有添加重定向函数,而是添加了两个重定向函数(分别添加在了sys.c和usart.c中)
在sys.c函数中定义了一次:
#pragma import(__use_no_semihosting)
struct __FILE {
int handle;
};
#if( defined DEBUG)
int fputc( int c, FILE *f )
{
#if DEBUG == Debug_UART0
while( R8_UART0_TFC == UART_FIFO_SIZE ); /* 等待数据发送 */
R8_UART0_THR = c; /* 发送数据 */
#elif DEBUG == Debug_UART1
while( R8_UART1_TFC == UART_FIFO_SIZE ); /* 等待数据发送 */
R8_UART1_THR = c; /* 发送数据 */
#elif DEBUG == Debug_UART2
while( R8_UART2_TFC == UART_FIFO_SIZE ); /* 等待数据发送 */
R8_UART2_THR = c; /* 发送数据 */
#elif DEBUG == Debug_UART3
while( R8_UART3_TFC == UART_FIFO_SIZE ); /* 等待数据发送 */
R8_UART3_THR = c; /* 发送数据 */
#endif
return( c );
}
#endif
在usart.c中又重复添加了一个重定向函数:
#pragma import(__use_no_semihosting)
struct __FILE {
int handle;
};
FILE __stdout;
void _sys_exit(int x)
{
x = x;
}
int fputc(int ch, FILE *f)
{
while(R8_UART1_TFC != UART_FIFO_SIZE)
{
R8_UART1_THR = ch;
}
return ch;
}
有人会觉得两次重定向了程序应该会报错,但不好意思程序真没报错。将sys.c中下面这段代码删除后再测试串口收发数据正常。
#pragma import(__use_no_semihosting)
struct __FILE {
int handle;
};
目前就遇到过这么几种常见(或不常见)的情况,后面会持续更新遇到的串口问题。