当前位置:首页 > 工业控制 > 工业控制
[导读]先说清楚:串口在变频器、焊机、大电机旁边做不到绝对“100%不丢”,工程上能做的是把误码率压到可接受、错误帧不生效、丢包自动补回来。单靠“校验一下CRC”不够,得物理层+协议层+重传层三层一起扛。


先说清楚:串口在变频器、焊机、大电机旁边做不到绝对“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补回丢失帧;最后错误计数让人能看见总线健康度。三层叠起来,工业现场电机启停、焊机打火时的偶发误码会被自动过滤或重发,业务层拿到的基本都是干净帧——这才是“可靠”的真实含义:不是永不出错,而是错的不生效、丢的能补回来。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭