简介:面向51单片机与工业变频器通讯调试的工程实例包,适合已掌握单片机基础、正在做RS-485、MODBUS或CAN总线通讯练习的电子与自动化方向学习者。资源围绕KEIL C环境下的UART配置、波特率与校验位设置、发送接收函数、MODBUS帧结构及CRC校验、变频器频率与启停寄存器读写等关键环节,提供可对照的工程骨架和编程思路。压缩包共14个文件,约21KB,以C源文件、A51汇编启动文件、Keil工程配置及编译中间文件为主,兼顾源码阅读与工程复现。已有971人学习下载,可作为课程设计、毕业设计或工业控制入门项目的参考,帮助读者快速理清硬件接口、协议交互与异常处理之间的组织关系。
1. 从一台只能按面板的老变频器说起
车间里有台老设备,变频器装在电控柜里,改频率得打开柜门按面板上的上下键,改完还得记着当前值,换班的人经常调错。想加远程启停和频率给定,最省事的做法不是换变频器,而是让一块 51单片机通过 RS485 跟变频器通讯,把「启停、频率给定、读运行状态」这三件事搬到操作面板上。这个方向技术门槛不高,但真做起来,51单片机与变频器通讯的坑集中在协议时序和电气层,不在业务逻辑。适合手上还有 8051 老平台、要做设备小改造的开发者,也适合刚学完串口想找个真实项目练手的人。
2. 协议与硬件:RS485 差分链路怎么搭、Modbus RTU 帧怎么排
变频器对外通讯,工业现场绝大多数走 RS485 上的 Modbus RTU。这套组合的好处是两线制、支持多从站、帧格式简单,51 这种资源紧张的芯片也能扛得住。先要把物理链路搭对,再谈协议。
2.1 从 TTL 到差分:收发器、方向控制和隔离
51 的 RXD/TXD 是 TTL 电平,只能板内走线,超过一两米就开始玄学。中间必须加一颗 RS485 收发器,把单端信号转成 A/B 差分。这类芯片常见的是 8 脚半双工封装,引脚就那几个:RO 接单片机 RXD,DI 接 TXD,DE 和 /RE 通常并在一起,用一个 IO 控制方向,A/B 接双绞线。
sbit RS485_DIR = P1^0; // DE 与 /RE 并接后接 P1.0 #define RS485_DIR_TX() (RS485_DIR = 1) // 拉高:发送 #define RS485_DIR_RX() (RS485_DIR = 0) // 拉低:接收方向控制是这套电路最容易翻车的地方。DE 拉高期间芯片占着总线,别的从站没法说话;拉低之后才进入高阻接收态。发完最后一个字节,必须等移位寄存器把停止位完整推出去了才能切回接收,否则最后半个字节会被自己掐掉。判断依据就是 TI 标志,这一点后面第 3 章会写死在代码里。
电气上有三件事值得花十分钟处理。第一,A/B 之间并在总线两端的 120Ω 终端电阻,只在物理两端各放一个,中间节点不要放,放多了总线负载过重,通讯距离直接砍半。第二,加偏置电阻把空闲态拉到确定的差分电平,避免总线悬空时接收端收到一堆随机跳变。第三,如果变频器和控制板不在同一个配电回路,建议在 RS485 侧做光耦隔离或者用带隔离的收发模块,两边不共地,能挡掉大部分共模干扰。
提示:现场布线用屏蔽双绞线,屏蔽层单端接地,不要两端都接,形成地环流之后干扰反而更严重。
2.2 Modbus RTU 帧结构:一帧 8 个字节能装下什么
Modbus RTU 的一帧由四段组成:从站地址、功能码、数据区、CRC16 校验。地址占 1 字节,范围 1~247,0 是广播地址(从站不回应答);功能码 1 字节;数据区长度随功能码变化;最后两个字节是 CRC,低字节在前、高字节在后,这一点和很多人的直觉相反。
对变频器控制来说,常用的功能码只有三个:
| 功能码 | 名称 | 典型用途 | 请求数据长度 |
|---|---|---|---|
| 0x03 | 读保持寄存器 | 读运行状态、输出频率、输出电流、故障码 | 4 字节 |
| 0x06 | 写单个寄存器 | 写启停命令、写频率给定值 | 4 字节 |
| 0x10 | 写多个寄存器 | 一次写启停 + 频率,减少帧数 | 4+N 字节 |
以 0x06 为例,一帧固定 8 字节:地址 | 0x06 | 寄存器高 | 寄存器低 | 数据高 | 数据低 | CRC低 | CRC高。从站正确收到之后,原样回显这 8 个字节;如果出错,回的是地址 | 0x86 | 异常码 | CRC,功能码最高位置 1,异常码告诉你错在哪,比如 0x02 是非法寄存器地址,0x03 是数据值超范围。
寄存器地址是最需要翻手册的部分。不同厂家布局不一样,但套路相似:一组是控制类,管启停和频率给定;一组是状态类,只读,返回运行状态字、输出频率、母线电压这些。下面这套地址仅作示例,实际以手上那台变频器的手册为准。
0x2000 通讯控制字 写 1 启动 / 写 0 停止 0x2001 频率给定 单位 0.01Hz,写 5000 表示 50.00Hz 0x2100 运行状态字 只读,bit0 运行、bit1 正反转、bit2 故障 0x2101 输出频率 只读,单位 0.01Hz 0x2102 输出电流 只读,单位 0.01A 0x2103 故障代码 只读,0 表示无故障需要注意频率给定单位。有的型号用 0.01Hz,有的用 0.1Hz,还有的直接用百分比。写错单位不会报错,电机会以你意想不到的速度转起来,这种事故在现场一点都不稀奇。
2.3 串口参数对齐:11.0592MHz 晶振和校验位那点事
51 做串口通讯,晶振选 11.0592MHz 不是随便定的,这个频率能整除出标准波特率,误差为 0。换成 12MHz 之后,9600 波特率的实际误差大概在 8% 左右,短帧勉强能跑,长帧就等着收错。
定时器 1 工作在方式 2(8 位自动重装)做波特率发生器,9600bps 下重装值是 0xFD:
void uart_init_9600_8n1(void) { SCON = 0x50; // 方式1:8 位数据,允许接收 TMOD &= 0x0F; TMOD |= 0x20; // 定时器1 方式2,自动重装 TH1 = 0xFD; // 9600bps @ 11.0592MHz,SMOD = 0 TL1 = 0xFD; PCON &= 0x7F; // SMOD = 0 TR1 = 1; ES = 1; // 开串口中断 EA = 1; }如果变频器设成 19200,把 SMOD 置 1(PCON |= 0x80),重装值仍然是 0xFD,波特率翻倍,这是 51 的一个便利之处。
麻烦在效验位。Modbus RTU 常见配置是 8E1(偶校验),但 51 的串口方式 1 只有 8 位数据位,硬件不会自动加校验位。要用方式 3,9 位可变波特率,第 9 位(TB8)手动填校验值:
void uart_send_byte_even(unsigned char dat) { unsigned char p = 0, i, tmp = dat; for (i = 0; i < 8; i++) { p ^= (tmp & 1); tmp >>= 1; } TB8 = p; // 偶校验:1 的个数凑成偶数 SBUF = dat; while (!TI); TI = 0; }方式 3 的初始化是SCON = 0xD0(SM0=1, SM1=1, REN=1),波特率算法和方式 1 相同。接收端校验位会进 RB8,程序里自己判断,对不上就丢掉这一字节。我一般优先让变频器改成 8N1,能省掉这一整段逻辑;如果现场不允许改参数,那就老老实实走方式 3。
3. 核心代码拆解:中断收帧、CRC16 与三个业务函数
协议和硬件定下来之后,代码分三层:最底下是字节收发,中间是帧组装与校验,最上面是业务封装。分层的目的很实在——换一款变频器,只改最上面一层。
3.1 环形缓冲区:中断只负责搬字节
串口中断里最忌讳做复杂判断。中断服务程序只干一件事:把 SBUF 里的字节塞进环形缓冲区,然后立刻退出。校验、解析、业务判断全部放到主循环。
#define RB_SIZE 64 // 必须是 2 的幂 static volatile unsigned char rb_buf[RB_SIZE]; static volatile unsigned char rb_head = 0, rb_tail = 0; static volatile bit tx_busy = 0; void uart_isr(void) interrupt 4 { unsigned char dat, next; if (RI) { RI = 0; dat = SBUF; // 先读走,清接收标志 next = (rb_head + 1) & (RB_SIZE - 1); if (next != rb_tail) { // 队列未满才写入,满了丢弃 rb_buf[rb_head] = dat; rb_head = next; } } if (TI) { TI = 0; tx_busy = 0; // 一个字节发送完毕 } } unsigned char rb_read(unsigned char *out) { if (rb_head == rb_tail) return 0; // 空 *out = rb_buf[rb_tail]; rb_tail = (rb_tail + 1) & (RB_SIZE - 1); return 1; }rb_head和rb_tail都声明成volatile,因为它们在中断和主循环里都会被改,编译器不知道这件事,不加就可能把读操作优化掉,表现是「中断明明收了数据,主循环死活读不到」。队列满时直接丢弃而不是覆盖,是为了保证已经收进来的半帧不被冲掉;丢字节总比收到一帧错位数据强。
3.2 CRC16:Modbus 的多项式与字节序
Modbus 用的 CRC16 多项式是 0xA001(0x8005 的反向表示),初值 0xFFFF,不取反、不异或输出。逐位算法在 51 上完全够用,8 字节一帧算下来不到 100 个指令周期。
unsigned int crc16_modbus(const unsigned char *buf, unsigned char len) { unsigned int crc = 0xFFFF; unsigned char i; while (len--) { crc ^= *buf++; for (i = 0; i < 8; i++) { if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001; else crc >>= 1; } } return crc; }返回值是 16 位整数,发送时要拆开,低字节在前:
unsigned int crc = crc16_modbus(tx, 6); tx[6] = crc & 0xFF; // CRC 低字节 tx[7] = crc >> 8; // CRC 高字节接收侧做校验时反过来拼:crc_recv = rx[n-1] << 8 | rx[n-2],拿它和crc16_modbus(rx, n-2)比较。这个字节序搞反了,现象是「变频器明明应答了,程序就是认为校验失败」,我第一次调的时候在这上面耗了大半天,血泪经验。
3.3 组帧与收发切换:mb_transfer是整个通讯的地基
所有业务函数最终都调这一个底层函数:发一帧,等应答,校验,返回结果。它必须处理好三件事——方向切换的时机、应答超时、异常帧识别。
/* * tx : 待发送帧(含 CRC) * txlen : 发送长度 * rx : 应答缓冲区,调用方保证至少 32 字节 * 返回值 : 0 成功;1 超时无应答;2 CRC 错;3 从站异常应答 */ unsigned char mb_transfer(unsigned char *tx, unsigned char txlen, unsigned char *rx) { unsigned char i, rlen = 0, dat; unsigned int wait; RS485_DIR_TX(); // 占用总线 for (i = 0; i < txlen; i++) { tx_busy = 1; SBUF = tx[i]; while (tx_busy); // 等 TI 置位,等移位寄存器发完 } RS485_DIR_RX(); // 最后一个字节发完才切回接收 /* 等首字节,最多 100ms */ wait = 0; while (!rb_read(&rx[0])) { if (++wait > 100) return 1; // 从站没应答 delay_1ms(); } /* 按功能码推算帧长,继续收完整帧 */ rlen = (rx[0] == 0) ? 0 : frame_len_by_func(rx); if (rlen == 0 || rlen > 32) return 1; while (rlen > 0) { if (rb_read(&dat)) { rx[ /* 见下方说明 */ ] = dat; } } /* 校验 */ if (crc16_modbus(rx, rlen - 2) != ((unsigned int)rx[rlen - 1] << 8 | rx[rlen - 2])) return 2; if (rx[1] & 0x80) return 3; // 异常应答,rx[2] 是异常码 return 0; }几个关键点值得单独拎出来。while (tx_busy)这一句是方向切换的正确姿势:TI 置位表示一个字节的停止位已经移出,此时切方向不会截断数据。如果图省事在写 SBUF 后立刻切,最后半个字节会丢失。等待首字节的 100ms 是经验值,变频器响应通常在 10~30ms,留到 100ms 能覆盖响应慢的型号。帧长推算依赖功能码,0x03 的应答长度由第 3 字节的字节数决定,0x06 的应答固定 8 字节,0x10 的应答固定 8 字节,异常应答固定 5 字节,这个函数写一次就通用。
上面的接收循环里我省略了索引细节,实际写法是先把 rx[0] 读出,然后用一个idx变量从 1 开始逐个填入,避免覆盖首字节。
3.4 三个业务函数:写频率、启停、读状态
有了mb_transfer,业务层就干净了,每个功能十几行。
/* 写单个寄存器,功能码 0x06 */ unsigned char mb_write_reg(unsigned char addr, unsigned int reg, unsigned int val) { unsigned char tx[8], rx[8]; unsigned int crc; tx[0] = addr; tx[1] = 0x06; tx[2] = reg >> 8; tx[3] = reg & 0xFF; tx[4] = val >> 8; tx[5] = val & 0xFF; crc = crc16_modbus(tx, 6); tx[6] = crc & 0xFF; tx[7] = crc >> 8; return mb_transfer(tx, 8, rx); } /* 设定频率,hz_x100 是 0.01Hz 为单位,例如 50.00Hz 传 5000 */ unsigned char vfd_set_freq(unsigned char addr, unsigned int hz_x100) { return mb_write_reg(addr, 0x2001, hz_x100); } /* 启停控制 */ unsigned char vfd_run(unsigned char addr, unsigned char on) { return mb_write_reg(addr, 0x2000, on ? 1 : 0); } /* 读输出频率,成功时 *hz_x100 带出 0.01Hz 单位的值 */ unsigned char vfd_read_freq(unsigned char addr, unsigned int *hz_x100) { unsigned char tx[8], rx[8]; unsigned int crc; tx[0] = addr; tx[1] = 0x03; tx[2] = 0x21; tx[3] = 0x01; tx[4] = 0x00; tx[5] = 0x01; // 读 1 个寄存器 crc = crc16_modbus(tx, 6); tx[6] = crc & 0xFF; tx[7] = crc >> 8; if (mb_transfer(tx, 8, rx) != 0) return 1; if (rx[2] != 2) return 1; // 应答数据区长度应为 2 字节 *hz_x100 = ((unsigned int)rx[3] << 8) | rx[4]; return 0; }vfd_set_freq传参用hz_x100这种「放大一百倍」的整数,而不是浮点,原因是 51 上跑浮点运算又慢又占空间,而变频器的频率分辨率一般就是 0.01Hz,整数刚好一一对应。这个命名习惯我建议保留,调用处一看就知道单位,能少掉很多「50 还是 5000」的口角。
vfd_read_freq里对rx[2]的判断是防止变频器返回嵌套异常或者半帧,属于防御性写法。现场调试阶段把mb_transfer的返回值打到数码管或者串口助手上,比盯着电机猜要高效得多。
4. 主循环架构:把「阻塞等应答」改成时间片轮询
第 3 章的写法在单任务里能跑,但一旦加上按键、显示、报警输出,while (tx_busy)和delay_1ms()这两个阻塞点就会让整个系统卡顿。按键按下去没反应,数码管闪烁,用户第一个感受就是「这板子做得不行」。要解决,得把通讯改成非阻塞的状态机。
4.1 3.5 字符间隔:判断一帧结束的硬标准
Modbus RTU 没有帧头帧尾,靠静默时间划界:一帧结束之后至少静默 3.5 个字符时间,下一帧才算新帧。9600bps、8N1 配置下,一个字符是 11 位(1 起始 + 8 数据 + 1 校验/无 + 1 停止),耗时约 1.146ms,3.5 个字符约 4.01ms。这个时间不能凭感觉给,给短了会把一帧劈成两半,给长了降低吞吐。
在 51 上实现,用一个 1ms 定时器中断计数,主循环发现「缓冲区有数据且静默超过 4ms」就判定收完一帧:
static unsigned char idle_ms = 0; static bit frame_ready = 0; /* 定时器0 中断,1ms 一次 */ void timer0_isr(void) interrupt 1 { TL0 = 0x66; TH0 = 0xFC; // 11.0592MHz 下 1ms 重装值 if (rb_head != rb_tail) idle_ms = 0; // 还有字节进来,静默清零 else if (idle_ms < 10) idle_ms++; if (idle_ms == 4) frame_ready = 1; // 4ms 静默,视为帧结束 }主循环里轮询frame_ready,置位了就把缓冲区里的字节读出来做 CRC 校验和解析,处理完清标志。这套逻辑比在中断里判断帧头靠谱得多,也不会漏帧——中断只要保证字节按时进队列,剩下的交给主循环。
4.2 命令队列:上层只管下单,底层负责送达
设备上真正需要发出去的请求不止一个:周期性读状态、按键触发的写频率、上位机下发的启停。如果都在主循环里直接调mb_write_reg,一旦某个请求超时,后面的全部堵住。做法是加一个先进先出队列,上层只入队,底层状态机按顺序取。
typedef struct { unsigned char addr; unsigned char func; // 0x03 或 0x06 unsigned int reg; unsigned int val; } mb_cmd_t; #define CMD_Q_SIZE 8 static mb_cmd_t cmd_q[CMD_Q_SIZE]; static unsigned char q_head = 0, q_tail = 0; unsigned char mb_post(unsigned char addr, unsigned char func, unsigned int reg, unsigned int val) { unsigned char next = (q_head + 1) & (CMD_Q_SIZE - 1); if (next == q_tail) return 0; // 队列满,拒收 cmd_q[q_head].addr = addr; cmd_q[q_head].func = func; cmd_q[q_head].reg = reg; cmd_q[q_head].val = val; q_head = next; return 1; }状态机按下图这四个状态转,全部在主循环的 10ms 时间片里推进,不占用中断,也不阻塞其他任务:
typedef enum { MB_IDLE, MB_SENDING, MB_WAITING, MB_RETRY } mb_st_t; static mb_st_t st = MB_IDLE; static unsigned char retry = 0; void mb_poll(void) // 每 10ms 调用一次 { switch (st) { case MB_IDLE: if (q_tail != q_head && frame_ready == 0) { build_frame(&cmd_q[q_tail]); // 组帧进发送缓冲 st = MB_SENDING; retry = 0; start_send(); } break; case MB_SENDING: if (send_done()) st = MB_WAITING; break; case MB_WAITING: if (frame_ready) { // 收到应答 frame_ready = 0; if (parse_and_check() == 0) { q_tail = (q_tail + 1) & (CMD_Q_SIZE - 1); st = MB_IDLE; } else st = MB_RETRY; } else if (++timeout_10ms > 30) { // 300ms 无应答 st = MB_RETRY; } break; case MB_RETRY: if (++retry >= 3) { report_comm_fault(cmd_q[q_tail].addr); // 报通讯故障 q_tail = (q_tail + 1) & (CMD_Q_SIZE - 1); st = MB_IDLE; } else st = MB_IDLE; // 重新组帧再试 break; } }MB_RETRY分支里重试三次仍然失败才报故障,这个阈值不是拍脑袋,是因为现场偶发的干扰帧很常见,一次失败就停机反而影响生产。三次都失败,大概率是线断了或者从站地址设错,这时候报故障才有意义。
4.3 通讯故障时变频器应该怎么办
这一点经常被忽略。控制板这边通讯断了,变频器自己不会停,它会保持最后一次收到的频率继续跑。有人觉得这是好事,其实风险很大——你按了停止键,屏幕显示已停,电机还在转。
正规做法是在变频器里把「通讯超时动作」参数打开,设一个超时时间(比如 1 秒),超时后按预设方式停机或降到安全频率。这个参数在变频器参数表里一般叫通讯断线处理或者通讯超时检测,名称各家不同,但都有。软件侧同时把通讯故障标志置起来,面板上显示错误码,两边都留痕,排查的时候才有依据。
注意:调试阶段把变频器接电机之前,先用面板确认电机转向和频率给定单位都对,再接通讯线。写错地址或者写错单位的代价,可能是一台设备直接报废。
5. 避坑与排查:现场最容易翻车的五件事
下面这几条基本都是我或者同行真实踩过的,按「现象 → 原因 → 解决」的顺序写,对号入座比看手册快。
5.1 电气与接线类问题
现象一:短距离调试好好的,接到现场十几米线就时通时断,CRC 错乱率很高。
原因通常不是协议问题,是共模干扰。控制板和变频器分别接在不同的配电回路,两边地电位有差值,双绞线上叠加了共模电压,接收器判错电平。另一种可能是终端电阻缺失或者放错位置。
解决:先量一下 A、B 对地的电压,超出收发器共模范围就得上隔离。加隔离收发模块或者光耦隔离,两边地彻底分开。同时确认总线两端各一个 120Ω,中间节点不接。屏蔽层改成单端接地。这三步做完,绝大多数现场干扰都能压下去。
现象二:上电之后总线完全没反应,用示波器看 A/B 一直是静态电平。
原因多半是 DE 方向脚一直拉高,芯片占着总线不放,或者方向脚接反了,单片机在收的时候芯片在发。
解决:在mb_transfer里加断点或用 IO 翻转配合示波器,确认发送期间 DE 为高、发送结束后立刻为低。空闲态必须处于接收。顺手检查 DE 和 /RE 是不是真并在一起了,有的板子把 /RE 悬空,结果芯片永远处于接收态,发不出去。
现象三:偶尔丢最后一两个字节,或者变频器报了帧错误。
原因是方向切换太早,最后一个字节的停止位还没移完就把 DE 拉低了,总线上出现半个字符。这类问题出现概率不高,因为长度不一样、时序余量不一样,属于典型的翻车型 bug。
解决:别在写 SBUF 后延时切方向,改成等 TI 置位。TI 是硬件告诉你「这个字节彻底发完了」的唯一可靠信号,用延时纯属赌运气。
5.2 协议与配置类问题
现象四:变频器有应答,但程序一直报校验错。
按概率排序,第一大原因是 CRC 字节序搞反,发送时高字节在前了;第二大原因是收到的字节数算错,把应答帧里的 CRC 也拿去参与计算了;第三大原因是用了 8E1 但串口初始化为 8N1,每个字节的第 9 位被丢弃,数据错位。
解决:第一步,用串口助手抓一帧变频器的原始应答,手动算一遍 CRC 对比,确认字节序;第二步,确认crc16_modbus(rx, n-2)里传的是n-2不是n;第三步,对齐双方校验位配置,改不了变频器就走方式 3 手动填 TB8。
现象五:能读到状态,但一写频率就报异常码 0x03,或者写进去电机转得飞快。
异常码 0x03 是数据值超出范围,常见于频率写超了上限,或者单位用错——程序按 0.01Hz 传了 5000,变频器按 0.1Hz 解读,直接变成 500Hz。也有型号要求先写使能位再写频率,顺序错了会被拒。
解决:翻手册确认三件事——频率寄存器的地址、单位、上下限。代码里加一层范围检查,超过上限直接截断并报错,不要盲目往下发。另外确认变频器是否处于「通讯控制」模式,很多型号需要把运行命令源切到通讯,否则写频率寄存器无效但不报错。
6. 二次开发:从单台控制到多从站轮询的三个改造点
一台变频器跑通之后,下一步通常是带多台——一条产线上三五台电机,都要走同一根 RS485。这套代码改起来工作量不大,但有三处必须动。
第一处是地址表。单台时从站地址写死在宏里,多台就得建一张表,把地址、频率上下限、用途都放进去。我一般用结构体数组,放在单独的头文件里,改机型只改这张表:
typedef struct { unsigned char addr; // 从站地址 1~247 unsigned int freq_min; // 下限,0.01Hz unsigned int freq_max; // 上限,0.01Hz unsigned char enable; // 1 参与巡检 } vfd_node_t; const vfd_node_t vfd_table[] = { { 1, 0, 5000, 1 }, { 2, 0, 5000, 1 }, { 3, 0, 3000, 1 }, };巡检时按表遍历,每次只发一个请求,收不到就跳到下一台,不阻塞整个轮询。注意总线上从站越多,一轮的耗时越长,三台在 9600bps 下轮一遍大约 100ms 出头,超过八台就得考虑提高波特率或者降低巡检频率。
第二处是帧长解析和超时时间要按从站数量自适应。超时时间不能固定 100ms,至少要是「最慢那台从站的响应时间 + 一帧传输时间」的两倍,否则轮询时总有一两台莫名超时。
第三处是日志。现场调试最怕黑匣子,出问题之后什么都查不到。我的习惯是留一个 32 字节的循环日志区,记录最近 8 次通讯的地址、功能码、返回码和耗时,出故障时可以用按键翻出来看。这块内存开销很小,但能把「用户说它坏了、你去了它又好了」这种玄学问题的定位时间从一天压缩到十分钟。
最后说个验证方法。改完代码别急着接真电机,先用一台变频器加串口助手模拟:助手扮演从站,手工回应答帧,检查主程序的超时、重试、CRC 校验、异常码分支是否都按预期走。这套回放测试花半小时,能挡掉后面现场大半的低级错误。
从那以后我每次做通讯改造,都会先在串口助手上把异常帧和超时帧手工跑一遍,确认程序能正确报错再上真设备——省下来的返工时间远超过这半小时。希望帮到你。
本文还有配套的精品资源,点击获取