写在前面
最近使用STM32做串口数据收发,遇到了一些问题。折腾了一番,在此记录一下。
0 需求
- 云平台通过“发布消息”,下行指令。
- 4G模块接收平台下行指令并转发到单片机,单片机通过串口(UART3)做数据接收与分析。
总的来说,比较简单。单片机和4G通过串口通信,当4G与平台连接之后,在保证数据在平台与4G模块之间能正常流转的情况下,可视为单片机使用串口直接与平台进行通信。而原子亦给出了串口通信的相关例程,其中包含串口收发实验,可做参考。
1 问题产生
为了实现需求,先进行两个小实验 3. 模块+上位机实验 : 验证4G模块与平台之间数据收发正常。 4. 电路板串口数据接收实验 :排除电路板硬件异常问题。
1.1 模块+上位机实验
平台发布消息,模块TX引脚输出。通过CH340与串口调试助手相连,接收并显示数据。验证模块能否正常收发数据。 ---------------------补一张串口接收数据图--------------
可以正常收发数据,排除模块问题。 ASCII编码对照表_911查询
1.2 电路板串口数据接收实验
实验硬件:UART1(COM3) , UART3(COM1)实验。 实验现象:UART1 接收到的消息通过UART1打印到串口;UART3 接收到的消息亦通过UART1打印到串口。(UART1做串口调试使用) 实验结论: 排除电路板硬件异常原因,UART1 & UART3 可以正常收发数据。 示例代码:
while(1)
{
if(USART_RX_STA&0x8000)
{
LED0 = 0; //点亮LED显示接收到消息
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\nUART1发送的消息为:\r\n");
HAL_UART_Transmit(&UART1_Handler,(uint8_t*)USART_RX_BUF,len,1000); //发送接收到的数据
while(__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_TC)!=SET); //等待发送结束
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}
else if(USART3_RX_STA&0x8000)
{
LED0 = 1; //熄灭LED显示接收到消息
len=USART3_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\nUART3发送的消息为:\r\n");
HAL_UART_Transmit(&UART1_Handler,(uint8_t*)USART3_RX_BUF,len,1000); //发送接收到的数据
while(__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_TC)!=SET); //等待发送结束
printf("\r\n\r\n");//插入换行
USART3_RX_STA=0;
}
}
1.3 问题来了!
当以上两个小测试完成时,可以确定整个系统软件与硬件无误。也就是说,当电路板上插上4G模块时,即可实现单片机与平台通信的功能! 然而,当平台发布数据时,单片机未能接收到数据。通过软件调试等操作发现,UART3并未进入到接收到数据中断。用示波器测量模块TX引脚,平台发布消息时确实有信号输出!迷惑行为,,,,,,
2 开始分析
2.1 串口数据格式
如下图,可以看出
- 无数据时,电平始终为‘1’
- 起始位为1位‘0’,停止位为1位‘1’
串口助手配置为: bound :115200 ;停止位 :1 ;数据位 :8 ; 检验位 : None
2.2 测一波波形
以上两个小测试确实可以验证软件与硬件无误。那么为什么单片机可以接收上位机传来的数据,而不能接收4G模块转发来的数据呢?二者数据有何区别? 验证方法 : 上位机和平台同时发送信息,测其输出信号。测试数据 11(0011_0001 0011_0001)
- 注 :串口助手及平台发布的数据为字符(ASCII),助手可选择hex发送及显示。
1. 平台发布数据 11(0011_0001 0011_0001) ,4G转发数据波形如图 **波形分析:**非常漂亮的波形,可以读出数据为 0011_0001 0011_0001(起始位为1位‘0’,停止位为1位‘1’) 2. 串口助手通过CH340发送数据 11(0011_0001 0011_0001) CH340 的 TX 引脚输出波形如下:X10_1000_1100_10_1000_1100_10_1011_0000_10_0101_0000_1X 参考串口数据格式,可知:串口发送的数据为:0011_0001 , 0011_0001 , 0000_1101 , 0000_1010
**波形分析:**前两个数据值为32,对应的字符为 ‘1’,‘1’,与发送数据一致。多了后两个值13 10,查询ASCII码为 0D 0A。对应换行键和归位键,嗯? 查看串口助手,果然!默认勾选了“发送新行” 再次测试,勾选了“发送新行”数据可以被接收;不勾选了“发送新行”,数据不被接收!可以看出“发送新行”即为单片机识别数据的“校验”格式。那么程序中一定有与“校验”相关的代码,那就找到他!
当直接接CH340输出时,图形在上。可以看到高电平只有1,5V左右,电压驱动并不强;当CH340与单片机相连时,发送波形测得的波形如下。当时怀疑过是高电平的问题,在单片机上接了个上拉电阻,波形是好看了,但依旧没有解决问题。因为串口低电平有效,并不要求严格的高电平。
3 代码分析
关于串口接收的代码如下,一眼就可看到关于0x0a,0x0d的判断,确认是结尾校验无误了。若要修改为 ‘**’ 校验,改为0x2a,0x2a即可。修改后测试成功,没图。
if(huart->Instance==USART3)//如果是串口1
{
if((USART3_RX_STA&0x8000)==0)//接收未完成
{
if(USART3_RX_STA&0x4000)//接收到了0x0d
{
if(aRx3Buffer[0]!=0x0a)USART3_RX_STA=0;//接收错误,重新开始
else USART3_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(aRx3Buffer[0]==0x0d)USART3_RX_STA|=0x4000;
else
{
USART3_RX_BUF[USART3_RX_STA&0X3FFF]=aRx3Buffer[0] ;
USART3_RX_STA++;
if(USART3_RX_STA>(USART_REC_LEN-1))USART3_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
- 注 :关于0x0a,0x0d 网上也有不少资料参考。原子的串口助手也有提示正确格式。由于很少使用16进制发送,一致没有注意到!
4 新的问题:串口数据累加
新的问题收测试时出现寄存器数据累加情况,具体表现为:
- 当串口UART3接收到的数据未加结束校验“**”,单片机未能判断数据接收完毕。
- 当下次数据来临(带校验),单片机判断数据发送完毕。通过串口1将数据输出。
再使用例程时,取消"发送新行",会出现同样的问题,由此可以判断是底层代码问题。 猜测:串口在接收未加校验的数据时,已经将数据存入串口接收缓冲buff中。当之后数据(带校验)来临时,继续将数据存入buff,并判断数据接收完毕。此时,多次发送的数据集中在同一buff中,数据为及时清空,由此导致数据累加的情况。 再来分析这段代码: 其中,USART3_RX_STA 是串口接收状态标记。定义为:?u16 USART_RX_STA=0; 功能如下 | bit15 | bit14 | bit13~0 | | ---------------- | -------------- | -------------------- | | 接收完成标志0x0a | 接收到0X0d标志 | 接收到的有效数据个数 |
if(huart->Instance==USART3) //如果是串口3
{
if((USART3_RX_STA&0x8000)==0) //接收未完成 USART3_RX_STA最高位判断
{
if(USART3_RX_STA&0x4000) //接收到了第一个0x2a USART3_RX_STA次高位判断
{
if(aRx3Buffer[0]!=0x0a)
{
USART3_RX_STA=0; //接收错误,重新开始
}
else USART3_RX_STA|=0x8000; //接收完成了
}
else //还没接收到第一个0x2a
{
if(aRx3Buffer[0]==0x0d)
{
USART3_RX_STA|=0x4000;
}
else
{
USART3_RX_BUF[USART3_RX_STA&0X3FFF]=aRx3Buffer[0] ; //0X3FFF USART3_RX_STA 低14位是数据
USART3_RX_STA++;
if(USART3_RX_STA>(USART_REC_LEN-1))
{
USART3_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
}
大致画了下流程图,时间关系。不再文字分析了,几个判断嵌套。串口通信实验讲解里关于USART_RX_STA的问题与思考这篇博客文字分析比较详细,推荐一波。 可以看出,与猜测一致。串口接收缓冲buff并没有进行数据清除。当数据(带校验)未临时,之前的数据会一直寄存在buff中,直到最终发送完毕。(当然数据累加有一定上限,USART_REC_LEN)
若要消除数据累加的情况,就必须在接收完一次不带校验的数据后,及时清除缓冲buff。实际使用中,两次接收数据之间有一定间隔,若能在间隔之中清除buff,即可规避。故加入以下代码,测试一下。
/* 间隔一定时间清空串口缓冲BUF USART3_RX_BUF */
if (time_10_ms) //定时器 10ms
{
time_10_ms = 0 ;
memset(USART3_RX_BUF, 0, sizeof USART3_RX_BUF);
}
测试结果:不加校验的数据串口不识别,定时清空处理,无数据累加情况。测试ok!
总结
没啥写的,强迫症凑一凑。
|