工业网关里的RS485,不是接上线就能通——一位嵌入式老兵的实战手记
去年冬天在山东某水泥厂做现场联调,客户指着屏幕上跳变的温度值问我:“你们这网关是不是不太稳?PLC读数老是乱跳。”我蹲在控制柜旁,用示波器夹住RS485的A/B线,看到一串毛刺叠加在正常信号上——不是协议栈写错了,是终端电阻没焊牢;不是CRC校验失败,是现场电工把屏蔽层接在了24V地而非信号地。那一刻我突然意识到:RS485的“稳定”,从来不在代码里,而在接线端子的拧紧力矩、PCB走线的差分阻抗、还有工程师对那3.5个字符周期的敬畏心。
所以这篇文字,不打算从“RS485是什么”开始讲起。我们直接钻进真实工业网关的固件里,看那一帧Modbus RTU是怎么从UART寄存器出发,穿过方向控制引脚,撞上总线上的噪声,又被状态机一丝不苟地拆解、校验、响应——全程没有抽象概念,只有寄存器位、毫秒计时、字节序陷阱和一个被反复打磨过的CRC表。
UART不是“串口驱动”,而是一组需要亲手拧紧的螺丝
很多人以为调通RS485,就是open("/dev/ttyS1", O_RDWR)然后write()。但当你真正面对i.MX6ULL或STM32H7这类SoC时,UART控制器是个沉默的机械师:它不会主动告诉你DE引脚该何时拉高,也不会提醒你UBMR寄存器填错一位,波特率误差就会飙到1.2%——而Modbus RTU容忍的极限是±1%。
以i.MX6ULL UART1为例,关键配置不是“打开串口”,而是三件事:
- 让RTS引脚彻底听你指挥
手册里写着UCR3_RTS能自动翻转RTS,听起来很美。但实际中,硬件自动切换的时机由发送移位器空标志(TC)触发,而TC置位时刻与最后一个字节真正离开收发器之间,存在不可控的传播延迟。更糟的是,某些收发器(比如老旧的MAX485)对DE建立时间要求苛刻——低于100ns就可能丢首字节。所以,我们宁可放弃“自动”,选择手动:
// GPIO1_IO19 复用为 RTS,但绝不让它自动翻转 IOMUXC_SetPinMux(IOMUXC_UART1_RTS_B_GPIO1_IO19, 0); IOMUXC_SetPinConfig(IOMUXC_UART1_RTS_B_GPIO1_IO19, 0x10B0); // 100kΩ下拉 → 默认接收态! // 发送前:拉高DE,等够建立时间 static inline void rs485_enter_tx(void) { gpio_set_level(GPIO_NUM_19, 1); // 这里不是“延时”,而是确保建立时间达标 // i.MX6ULL @ 528MHz,10次nop ≈ 20ns;我们保守取100ns → 500次nop for(volatile int i = 0; i < 500; i++); } // 发送完成后,在TX中断里拉低DE void uart1_tx_isr(void) { if (UART1->USR2 & USR2_TXDC) { // TX complete rs485_enter_rx(); // 此时才安全切回