1.背景
在原来的文章中,已经测试了RS485的电路
经过多次测试,没有问题。
但是在通信的过程中,偶尔会发生错误。
2.问题集合
2.1通信错位问题
2.1.1 问题描述
最近某台设备采用的MODBUS协议,返回的数据格式应该是
1 3 4 ef 8e 0 f ee ac
但是实际收到的数据是:
ee 1 3 4 ef 8e 0 f ee
上面的数据出现了数据错位
2.1.2 解决方法
1)首先要保证uart的rx引脚配置为上拉,不要外接下拉电阻;因为uart的通信起始位 是低电平开始,可能会导致数据判断错误;
2)rs485为半双工通信,在数据发送结束后,需要等待一段时间后再去接收数据;
这是因为,虽然uart已经发送完成了,但是485驱动器还需要切换电平,硬件缓冲区可能已清空,但驱动器可能还需要一点时间将最后一个位完全驱动到总线上。立即切换可能导致最后一个位被截断。这个延迟通常需要至少 1 个字符的传输时间(例如,对于 115200bps,1 个字符 10 位:10 / 115200 ≈ 87us)。更保守的做法是等待 2-3 个字符时间。可以使用简单的 for
循环延时或定时器实现。
3)程序处理框架
HAL_StatusTypeDef RS485_SendData(uint8_t *pData, uint16_t Size, uint32_t Timeout) {
HAL_StatusTypeDef status;
RS485_DIR_TX(); // 切换到发送模式
status = HAL_UART_Transmit(&huart1, pData, Size, Timeout); // 阻塞发送
if (status == HAL_OK) {
// 等待发送寄存器真正空 (TC 标志)
while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) == RESET) {}
// 关键延迟:等待最后一个位完全发出 (至少 1 个字符时间)
// 这里使用简单的循环延时,实际应用中建议使用定时器或计算精确时间
volatile uint32_t delay = SystemCoreClock / 1000000 * 100; // 大约 100us 延时 (根据你的波特率调整!)
while (delay--);
}
RS485_DIR_RX(); // 切换回接收模式 (无论发送成功与否)
return status;
}
// 接收数据函数 (与普通 UART 接收一样)
HAL_StatusTypeDef RS485_ReceiveData(uint8_t *pData, uint16_t Size, uint32_t Timeout) {
return HAL_UART_Receive(&huart1, pData, Size, Timeout);
}
2.1.3 要点
1)切换时机是核心: 过早切换回接收模式会截断自己发送的数据;过晚切换会错过总线上的响应或增加总线冲突风险。发送完成后的延迟至关重要。
2)延迟计算: 延迟时间需要根据你的波特率计算。例如
(1)波特率 9600bps:1 位时间 ≈ 104us,1 个字符 (10 位:1 起始 + 8 数据 + 1 停止) ≈ 1.04ms。延迟建议 > 1.04ms。
(2)波特率 115200bps:1 位时间 ≈ 8.68us,1 个字符 ≈ 86.8us。延迟建议 > 87us。
(3)使用 HAL_Delay()
进行毫秒级延迟通常足够,但对于高波特率,需要更精确的微秒级延迟(如定时器或 DWT
计数器)。