这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » STM32 » 分享一下在STM32系统中移植MODBUS遇到的串口发送问题

共1条 1/1 1 跳转至

分享一下在STM32系统中移植MODBUS遇到的串口发送问题

高工
2026-07-20 13:59:38   被打赏 45 分(兑奖)     打赏

    最近在项目中使用的RS485的采集温湿度的模块功能,所以这里我使用的一个开源的MODBUS库进行温度的采集,这里为了防止数据的丢失,我又使用的队列方式进行每帧数据的发送。

一:主机发送代码如下所示:

void USART2_IRQHandler()
{
    HAL_NVIC_ClearPendingIRQ(USART2_IRQn);
    if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_RXNE)!=RESET))
    {
            __HAL_UART_CLEAR_FLAG(&huart2,UART_FLAG_RXNE);
            mbh_uartRxIsr();
    }
    if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_TXE)!=RESET))
    {
            __HAL_UART_CLEAR_FLAG(&huart2,UART_FLAG_TXE);
            mbh_uartTxIsr();
    }
}

这里使用的是判断串口2,UART_FLAG_TXE状态位进行数据发送可以正常的发送,但是如果使用判断的UART_FLAG_TC的状态位方式,会导致代码只能发送一帧数据。后来使用判断UART_FLAG_TXE的状态位时候就正常显示。

if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_TC)!=RESET))
{
   __HAL_UART_CLEAR_FLAG(&huart2,UART_FLAG_TC);
   mbh_uartTxIsr();
}

UART_FLAG_TC 和 UART_IT_TC 虽然都指向“发送完成”这一事件,但它们的本质、用途和操作方式截然不同。

通常情况下,发送一帧长数据(如 10 个字节)的理想流程是:

  1. 启动:关TC,开TXE,发送第 1 个字节。

2.TXE中断(循环 2~9 次):TDR 空了,马上填入下一个字节,直到填完第 9 个。

3.最后一次TXE中断:填完第 10 个(最后一个)字节后,立即关闭 TXE中断,开启TC中断。

4.TC中断:最后一个字节的停止位已在物理线路上发送完毕。在这里做收尾工作(关TC中断、置标志位、切换 RS485 方向等)。

二:两个中断的对比:

触发条件:

UART_IT_TXE:发送数据寄存器(TDR)为空时立即触发。

UART_IT_TC:发送移位寄存器为空,且当前帧(含停止位)已物理发完时触发。

触发频率:

UART_IT_TXE:如果你不关中断且不写新数据,它会无限触发(因为 TDR 始终是空的)。

UART_IT_TC:每发送完一帧数据,只在结束时触发一次。

主要用途:

UART_IT_TXE:连续发送多字节数据(如发送一长串字符串)。在中断里填下一个字节,实现“流水线”发送。

UART_IT_TC:帧结束收尾工作(如发送完最后一个字节后关中断、释放总线、切换 RS485 为接收模式)。

中断响应速度:

UART_IT_TXE:极快(只涉及 CPU 内部寄存器搬运,不等待物理电平变换)。

UART_IT_TC:较慢(需要等待完整的波特率时间,比如 115200 下约 87us 才能触发)。

初始状态:

UART_IT_TXE:MCU 复位后,TDR 默认为空,TXE标志位默认为 1。

UART_IT_TC:MCU 复位后,TC 标志位默认为 1(因为无事可做,自然处于“完成”状态)。

三:遇到的问题

1:问题一:因为 TXE 标志位在 TDR 空时一直为 1,如果你使能了 UART_IT_TXE但没有数据要发,程序会瞬间无限进入中断,卡死主循环。

只有在有数据需要发送时,才使能TXE中断。发送完最后一个字节后,必须立即禁用TXE中断,以免它空闲时反复触发。

2:发送完最后一个字节时候:

如果你的代码在TXE中断里发最后一个字节,发完后不能立即关闭外设(或切换方向),因为此时数据还在移位寄存器里没发出去。

正确做法:在TXE中断里发送最后一个字节后,应关闭TXE中断,转而使能TC中断。等到TC中断触发时,再安全地关闭 UART 或切换RS485方向引脚。

3.刚初始化时的虚假中断

由于复位后TXE和TC默认都是 1,如果你在初始化后立即打开这两个中断,会立刻触发一次无意义的中断。

常见规避:在使能中断前,先调用一次 __HAL_UART_CLEAR_FLAG清空标志位;或者在初始化后先发送一个空数据或无用数据来“消耗”掉第一次触发。

四:调试后记

连续高效地“喂”数据给 UART,用TXE(注意及时关闭)。

确认数据已完整“送达”外部设备,或做发送结束清理,用TC。

这里我使用的是一个字节一个字节的发送,使用TXE中断更加的合适一些,如果我们使用DMA的方式进行数据发送,使用UART_IT_TC的中断更加的合适。





关键词: 串口发送问题    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]