工业现场抗干扰编程:在强电磁环境下保证串口通信100%数据可靠
先说清楚:串口在变频器、焊机、大电机旁边做不到绝对“100%不丢”,工程上能做的是把误码率压到可接受、错误帧不生效、丢包自动补回来。单靠“校验一下CRC”不够,得物理层+协议层+重传层三层一起扛。
一、物理层先别让软件背锅
RS485强干扰场景,固件再稳也救不了烂布线:
屏蔽双绞线,A/B双绞,别用普通两芯线
终端电阻120Ω只放总线两端,中间节点不加
屏蔽层单点接地(主站侧),从站悬空,杜绝地环路
收发器与MCU间加光耦/数字隔离,切断共模耦合
A/B线前级共模电感+TVS,钳位浪涌
远离动力线≥30cm,平行不行就垂直交叉
半双工DE/RE切换留≥1ms转向死区,别发送完立刻切接收
物理层稳了,软件层才不会天天在重传里救火。
二、帧格式:定长头+长度+CRC尾
别用“收到一串字节就解析”,UART是字节流,必须靠帧边界切包:
[0xAA][0x55][ADDR][FUNC][LEN][DATA...][CRCL][CRCH]
关键点:CRC覆盖范围包含ADDR/FUNC/LEN/DATA,不只数据段。否则地址被干扰从0x01变0x02,数据CRC还对,从站会误收别人指令。
三、CRC16别只校验数据段
Modbus常用CRC-16/MODBUS(多项式0xA001反射):
uint16_t crc16_modbus(const uint8_t*d, int len){
uint16_t crc=0xFFFF;
for(int i=0;i<len;i++){
crc ^= d[i];
for(int b=0;b<8;b++){
if(crc&1) crc = (crc>>1)^0xA001;
else crc >>= 1;
}
}
return crc;
}
/* 校验整帧(除CRCL/H) */
int frame_check(uint8_t*f, int len){
if(len<6) return -1;
uint16_t calc = crc16_modbus(f, len-2);
uint16_t recv = f[len-2] | (f[len-1]<<8);
return (calc==recv)?0:-1;
}
收到帧先length越界检查,再CRC,别倒过来——长度被改了会越界读。
四、接收状态机:空闲中断+环形缓冲
用UART空闲中断(IDLE)切帧,DMA搬数据,主循环只解析:
volatile uint8_t rx_buf[128];
volatile uint16_t rx_len;
volatile uint8_t rx_done;
void uart_idle_irq(void){
rx_len = DMA_GetCurrDataCounter();
rx_done = 1;
}
void comm_task(void){
if(!rx_done) return;
rx_done=0;
uint8_t*f = (uint8_t*)rx_buf;
if(frame_check(f, rx_len)==0){
handle_frame(f, rx_len);
send_ack(f[2], f[3]); // 回ACK
}else{
stat_crc_err++;
send_nak(f[2]); // 回NAK或沉默
}
}
粘包场景:两帧连发时单靠空闲中断可能合并,解析时按LEN字段逐帧偏移切,别假设一次IDLE就是一帧。
五、重传:超时+序号+最多3次
只校验不重发,强干扰下照样丢命令。发送方维护未确认队列:
#define MAX_RETRY 3
#define ACK_TO_MS 300
int send_reliable(uint8_t*frame,int len){
for(int i=0;i<MAX_RETRY;i++){
uart_send(frame,len);
uint32_t t0=sys_ms();
while(sys_ms()-t0 < ACK_TO_MS){
if(ack_matched(frame)) return 0;
}
stat_retry++;
}
stat_fail++;
return -1; // 三次失败报通信中断
}
要点:
超时按波特率调,115200可缩到100~200ms,9600放宽到500ms
帧带1字节seq,ACK回seq才认账,防旧ACK误匹配
重传失败别死循环,上报告警让上位机介入
多从站轮询别并发发,RS485半双工总线同一时刻只能一方说
六、错误计数与降级
工业现场要“看得见”干扰:
crc_err计数超阈值→波特率降一档(115200→19200)
连续NAK→该从站标离线,周期拉长慢轮询
电机启停时段错误突增→正常,重传兜住就行
重要控制指令:写线圈/置位必须等ACK,读保持寄存器可容忍一次失败
if(stat_crc_err > 50){
baud_drop();
stat_crc_err=0;
}
七、落坑清单
屏蔽层两端接地→地环路干扰更强
中间节点加终端电阻→等效阻抗拉低信号畸变
CRC只算DATA→地址被改检测不出
无帧头帧尾→粘包错位全解析错
收到就执行不ACK→丢帧无声
重传无限次→总线堵死
DE/RE切换无死区→首字节丢
校验失败还处理数据→误动作继电器
波特率硬顶115200跑长线→反射丢码
不统计错误率→现场越跑越烂发现不了
八、落地顺序
布线:屏蔽双绞、两端120Ω、单点接地、隔离收发
定帧格式:帧头+ADDR+FUNC+LEN+DATA+CRC
CRC16覆盖整帧除CRC自身
IDLE中断+DMA收,主循环校验
校验失败回NAK或不回,发送方超时重传
seq匹配ACK,最多3次
错误计数超阈值降波特率/标离线
关键指令必须ACK确认再执行
串口抗干扰不是“加个CRC”就结束。物理层把共模、反射、地环先消掉;协议层用定长帧边界+整帧CRC挡住比特翻转;重传层用序号+超时+ACK补回丢失帧;最后错误计数让人能看见总线健康度。三层叠起来,工业现场电机启停、焊机打火时的偶发误码会被自动过滤或重发,业务层拿到的基本都是干净帧——这才是“可靠”的真实含义:不是永不出错,而是错的不生效、丢的能补回来。





