你有没有遇到过这种情况:用RS485通信,主机发送命令,从机回复数据。大部分时候正常,偶尔丢最后一个字节。你查代码、查波特率、查终端电阻,一切正常。最后用示波器一看——发送完最后一个字节,还没等数据真正发完,你就把485芯片切到接收模式了。最后一个字节的最后一个位,被“腰斩”了。这就是RS485半双工通信最经典的坑:收发切换时机不对。

那个“方向控制”的引脚,RS485芯片,比如MAX485,有一个引脚叫RE/DE(接收使能/发送使能)。高电平:发送模式;低电平:接收模式RS485是半双工的,同一时刻只能发或只能收,不能同时。所以,你需要在发送前把RE/DE拉高,发送完再拉低,切回接收模式。

问题就在“发送完”这三个字上。那个“发送完成”的误解,很多人以为,调用完发送函数,数据就发完了。比如:

HAL_UART_Transmit(&huart2, buffer, len, 100);

HAL_GPIO_WritePin(RE_DE_GPIO_Port, RE_DE_Pin, GPIO_PIN_RESET); // 切回接收

HAL_UART_Transmit返回时,数据真的发完了吗?没有。它只是把数据写进了USART的发送数据寄存器(DR)。USART硬件会把这个字节一位一位地发出去,这个过程需要时间。对于9600bps,一个字节(10个位)需要约1.04ms。如果你在HAL_UART_Transmit返回后立即切换方向,最后一个字节才发了不到一半,就被切断了。丢失的,就是最后那个字节。

那个“TC标志”的等待,正确的做法是:等待发送完成标志(TC)。USART有一个状态寄存器SR,里面有一个位叫TC(Transmission Complete)。当最后一个字节的最后一个位发送完毕,硬件会把TC置1。所以,正确的代码应该是:

HAL_UART_Transmit(&huart2, buffer, len, 100);

while(!(USART2->SR & USART_SR_TC)); // 等待发送完成

HAL_GPIO_WritePin(RE_DE_GPIO_Port, RE_DE_Pin, GPIO_PIN_RESET); // 切回接收

等TC标志置1了,再切换方向。那个“微秒级”的窗口,发送一个字节的时间,取决于波特率。9600bps → 约1.04ms;115200bps → 约87μs;1Mbps → 约10μs,波特率越高,切换窗口越窄。如果你的程序里还有其他中断,稍微一耽误,就可能错过TC标志,或者切换晚了,漏掉从机发回来的数据。高波特率下,时序控制要精确到微秒级。

那个“硬件自动”的方案,有些高级的RS485芯片,有自动方向控制功能。它们监测TX线上的数据,当检测到数据发送时,自动把RE/DE拉高;发送完最后一个停止位,自动拉低。完全不用软件操心,硬件搞定。如果你的应用对时序要求很高,或者波特率很高,这种芯片值得考虑。

那个“从机响应”的延迟,还有一个细节:从机收到命令后,需要时间处理,才能回复数据。这个时间通常有几毫秒到几十毫秒。如果你切回接收模式太早,从机还没准备好,你收不到数据。如果你切回太晚,从机发的第一个字节,你可能漏掉。所以,主机发送完命令后,要等一个“响应延迟”,再切回接收。这个延迟时间,要在从机数据手册里查,或者实测确定。

这个故事给我们的启示,为什么RS485收发切换会丢最后一个字节?因为你太急了。你以为数据“交给USART”就算发完了,但USART还没发完。USART说:我还在发,你别急。你不等,就切断了。最后一个字节,死在了路上。这不是USART的错,不是485芯片的错,是代码的错。写在最后,下次你再写RS485代码,别只调用发送函数。加一行等待:

while(!(USARTx->SR & USART_SR_TC));

等TC标志亮起来。TC亮了,才是真的发完了。TC亮了,才能安全切换。那几微秒的等待,救的是你的数据。


(本文灵感源于于振南《新概念ARM32单片机》教程中对RS485收发切换时机的深刻讲解,感谢作者将半双工通信的时序陷阱讲得如此通透。)


如果您觉得这个故事对您有启发,欢迎点赞、转发,让更多工程师看到这个藏在TC标志背后的“发送完成”真相。关注我,一起探索嵌入式世界里那些“晚一步就丢数据”的硬核陷阱。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐