
简介面向三菱Q系列PLC工程师与工控入门者的MODBUS通信实战资料聚焦QJ71C24N串口模块的RTU配置与G.CPRTCL指令应用解决PLC与变频器等外部设备数据读写对接的实际问题。压缩包共1个doc文件约854KB为图文混排的实操笔记涵盖模块起始XY地址设定、开关设置中CH2协议与通信速度配置、通信协议支持功能里功能码03读保持寄存器的建立、软元件批量设置与模块写入流程。文中结合安川A1000变频器案例给出D7000D7002地址分配、协议执行个数与标准RTU接线说明并附G.CPRTCL各参数含义及多协议连续执行的软元件排布可作为现场调试的对照手册与排错参考。已有6752人学习。1. 一次 MODBUS 读取为什么梯形图里连一帧报文都看不见第一次翻别人的 QJ71C24N 工程很多人会觉得有点玄学整个程序里找不到 CRC 计算找不到卡 3.5 字符静默期的定时器甚至连一个发送缓冲区的影子都没有只有一行G.CPRTCL和一堆D7000、D7052这样的软元件地址。可它偏偏能把变频器的输出频率稳定读上来。原因不复杂——MODBUS RTU 的整条协议栈被搬进了模块内部CPU 只负责“给参数、收结果”。搞懂这次“搬家”搬走了什么、留下什么你才看得懂那些 D 区地址为什么这么排也才能在通信不通报错时判断该查接线、查指令参数还是查模块里那份没写进去的协议表。2. 协议栈搬家一条 G.CPRTCL 背后模块替你干了多少活2.1 3.5 个字符的静默期帧边界为什么不能靠扫描周期去卡MODBUS RTU 没有起始符也没有结束符它分辨一帧的开始和结束全靠总线上的“安静时间”帧与帧之间至少 3.5 个字符时间的静默帧内字符间隔不能超过 1.5 个字符时间。这个规定看起来只是协议细节落到 PLC 上却是个硬约束。以 9600bps、8 数据位、偶校验、1 停止位算一个字符 11 位约 1.15ms3.5 个字符就是 4ms 左右1.5 个字符约 1.7ms。问题在于 CPU 的扫描周期是会抖的。一段看起来“很短”的逻辑实际扫描时间可能在 1ms 到十几毫秒之间浮动中间还夹着中断、通信服务、其他智能模块的刷新。你想用梯形图去保证“两个字节之间不超过 1.7ms、两帧之间安静 4ms”本质上是在用软件去赌硬件的时序窗口赌赢一次容易连续跑三个月不翻车很难。这就是为什么早期自己拼帧的项目现场经常表现为“白天好好的晚上负载一高就 CRC 错”。模块的做法是把这件事交给自己的 UART 和硬件定时器。字节进出、字符间隔、帧尾静默全在模块的通信控制芯片里完成CPU 侧看不到。你在 GX Works2 里做的那些设置——CH2 的通信协议选 MODBUS RTU、通信速度、数据位、校验位、停止位——本质是在配置这个硬件状态机而不是在写软件逻辑。提示通信速度、数据位、校验方式、停止位这四项必须和从站设备完全一致。少一位校验现象不是“明显报错”而是全部超时或者偶发数据错位排查起来非常费时间。2.2 协议表被写进模块一份“预编译帧序列”长什么样理解了时序归硬件管再看那份“协议”就顺了。在 GX Works2 里走一遍工具 → 通信协议支持功能 → 串行通信模块新建再点添加会跳出一个协议库里面按功能码列好了条目——本例选的是 MODBUS 的读保持寄存器功能码 03。添加完成后模块里就多了一条“协议”它包含两部分发送侧知道自己要按什么顺序把从站地址、功能码、寄存器地址、点数拼成字节并补 CRC接收侧知道回帧应该多长、怎么校验、收到之后把数据搬到哪个软元件。你在参数里填的 D7000、D7001、D7002只是喂给这条协议的三粒“输入”。这套配置最后不是留在工程文件里而是用 在线 → 模块写入 写进模块本体。这一步很多人会忽略它的意义协议表存在模块里等于把“怎么说话”这件事从 CPU 程序中剥离了。换一台 CPU、重写一遍梯形图只要模块没换、协议表还在通信逻辑不用重做。反过来你要是只下载了程序没做模块写入程序里那行G.CPRTCL指向的是一个不存在的协议编号模块只会给你一个异常结束。和“无顺序协议自己拼帧”对比一下差别很直观环节模块内置协议梯形图自己拼帧帧结构组装模块按协议表生成CPU 逐字节 MOVCRC16 计算模块内部CPU 每帧算一遍3.5 字符帧边界硬件定时定时器 扫描周期难保证换从站/换寄存器改几个 D 区参数改逻辑出错定位协议执行结果 错误代码全靠自己打的标志位真正值钱的不是省了那几行 MOV是那 4ms 的静默期不再由你的扫描周期负责。2.3 一次调用的时序从执行条件 ON 到完成位上升沿G.CPRTCL是异步指令这一点决定了整个程序的写法。执行条件 ON 的那个扫描周期指令把“我要执行第几个协议、参数在哪”告诉模块然后就返回了CPU 继续扫下一行。模块在后台跑串口事务把请求帧发出去、等回帧、校验、把数据写进控制区。整个链路大致是这样几步执行条件 ON指令读取 (S) 起始的控制区取出从站地址、寄存器地址、点数以及 (S) 区里指定的协议编号。模块按协议编号取出对应的预编译帧序列拼出 8 字节请求帧补上 CRC从这个通道发出去。等待从站响应。这段时间 CPU 完全不管扫描照常进行。收到回帧校验 CRC、校验从站地址和功能码通过后把数据写进控制区对应位置。完成标志 (D) 置 ON。程序在 (D) 的上升沿取数据、做后续处理然后把启动条件复位。第 5 步是关键。新手最常见的写法是把取值逻辑和启动指令放在同一段无条件执行的逻辑里结果永远拿到上一次的值或者第一次拿到全零看着像通信慢半拍其实是时序没对上。稳妥的做法是把启动条件做成脉冲完成位 ON取数复位启动条件等下一个轮询节拍再拉起。3. G.CPRTCL 指令解剖Un、n1、n2 与 D 区的地址算术3.1 起始 XY 地址怎么变成 Un打开 GX Works2 新建工程右键智能功能模块安装位置要按硬件实际插槽填这里的“起始 XY 地址”就是后面所有事情的根。假设填的是 0090那么模块占用的 I/O 编号从 0090 开始G.CPRTCL的第一个操作数 Un 就指向这个起始 I/O 号实际写成H90这一档。原文示例里出现的写法是 H9 和 0090 混着来很多人看到这儿会懵。这不是笔误而是不同软件版本、不同显示位置对末位那个 0 的省略习惯不同造成的。判断标准只有一个以模块属性里实际读到的起始 I/O 号为准别照抄别人工程里的十六进制常数。; 缓冲存储器设置关闭回波示例中起始 I/O 为 0090Un 写作 H90 TOP H90 H1C2 K1 K1 ; Un 缓冲 点数 写入值 ; 地址 地址这段TOP指令直接写模块的缓冲存储器绕过了协议层。H1C2 是缓冲存储器地址K1 表示写一个点第二个 K1 是把该位置 1。这一位的作用是“禁止回波传送”为什么需要它后面第 5 章会展开——它是两线制 485 上最容易忽略的一环。写缓冲存储器这类操作地址和含义一定要对着模块手册的缓冲存储器映射表核不要凭记忆。Un 写错的后果比想象中严重指令不会报“参数非法”它会跑到 I/O 编号对应的那个模块上去访问缓冲存储器。运气好是执行异常运气不好是把旁边那个模块的配置改掉了。3.2 控制数据区 (S)D7000 到 D7052 各自装什么(S) 是控制数据的起始软元件示例用 D7000。指令执行时模块从这里读参数也往这里写结果。原文示例里几个关键位置的含义是这样的软元件示例值作用D7000从站站号外部设备的 MODBUS 从站地址D7001H24要访问的寄存器地址0024HD7002K2读取的寄存器个数D7052协议编号本次执行的协议功能码 03 那条新手最容易问的问题是为什么参数在 D7000、D7001、D7002 连着放协议编号却跑到 D7052 那么远因为这段控制区不是“三个参数挨着排”而是按“一次调用可连续执行最多 8 个协议”设计的编号区要留出 8 个位置依次对应第 1 次到第 8 次执行的协议编号参数区和结果区也要按协议次序铺开。原文里那句“(Sn2) 第一次执行的协议编号……(Sn9) 第八次”说的就是这套编号区的排布。这里有个必须点破的地方不同软件版本、不同协议数量下这些偏移的具体基址会变网上抄来的 D 区地址表经常对不上号。正确做法是用工具自身的功能去看——工具 → 设置软元件一览显示它会把这个工程实际用到的软元件布局列出来包括输入参数、输出结果、完成标志。以这份清单为准比背任何一张表都可靠。; 参数区准备每个协议用到的参数按工具导出的软元件一览填 MOV K1 D7000 ; 从站站号本例变频器站号 1 MOV H24 D7001 ; 起始寄存器 0024H MOV K2 D7002 ; 连续读 2 个寄存器0024H、0025H MOV K1 D7052 ; 第一次执行的协议编号功能码 03 那条代码里每一行都不是“随便放个数”而是在填协议执行时需要的槽位。MOV H24 D7001里的 H24 是十六进制常数表示 0024H 这个寄存器地址注意别写成十进制 24——十进制 24 是 0018H读回来的是另一个量而且往往“有数据但不对”比读不到更难查。3.3 完成标志 (D)什么时候该读数据什么时候该看异常(D) 在示例中被定义成“通信结束时 ON 的标志位”用位软元件比如 M200。但“结束”不等于“成功”这是必须分清的。常见的实现思路是给 (D) 配一个伴生位(D) 本身表示执行完成(D)1 表示异常结束。程序里判断的顺序应该是先看异常位再看数据。只看完成位就去解析数据遇到从站掉线、寄存器地址越界、CRC 错这类情况D 区里躺的还是上一次的有效数据你会以为通信正常只是数据“偶尔不准”。; 完成处理先用异常位兜底再取值 LD M200 ; 完成标志 ANI M201 ; 没有异常结束才取数 MOV D7003 D100 ; 第一个寄存器的值 → 频率 MOV D7004 D101 ; 第二个寄存器的值 → 电压 LD M200 RST M100 ; 复位启动条件等下一个轮询节拍取值之前先卡住异常分支取值之后立刻复位启动条件这个顺序不能反。复位放在取值后面是为了防止下一个扫描周期启动条件还没稳住又触发一次复位放在异常分支之外是为了保证无论成功失败启动条件都能收回来不至于卡死在一帧失败的通信上。4. 读保持寄存器 03从软元件参数到线上 9 个字节4.1 03 的请求与响应字节级拆解功能码 03 是 MODBUS 里最常用的读保持寄存器。它的请求很短固定 8 个字节响应长度随点数变化是 52N 字节。以读 2 个寄存器为例位置请求8 字节响应9 字节1从站地址从站地址203033起始寄存器高字节数据字节数044起始寄存器低字节第 1 个寄存器高字节5寄存器数量高字节第 1 个寄存器低字节6寄存器数量低字节第 2 个寄存器高字节7CRC 低字节第 2 个寄存器低字节8CRC 高字节CRC 低字节9—CRC 高字节从这张表能看出两件事。第一MODBUS 的寄存器是 16 位、大端序高字节在前PLC 侧读回来要按同样顺序拼拼反了会得到一个“看起来像那么回事”的数值比如频率 50.00Hz 变成 12800 之类。第二CRC 在报文里是低字节在前这跟数据部分的高字节在前正好相反手工算过 CRC 的人基本都在这里踩过坑。寄存器 0024H 和 0025H 这两个地址来自设备手册原文工程带的变频器手册把输出频率放在 0024H、输出电压放在 0025H。不同品牌、不同型号的寄存器映射完全不一样别把一张表套到所有设备上。另外要注意有些量是 32 位的比如累计电量、运行时间会跨两个连续寄存器这时点数要 K4回来的 8 个数据字节还得自己拼成双字。还有一层容易被忽略寄存器里放的是原始值工程单位要按设备手册的缩放比例换算。同样是 5000可能是 50.00Hz也可能是 5.000Hz全看手册怎么写。4.2 MOV H24 D7001 / MOV K2 D7002 的参数语义回到那两行看似平淡的指令。MOV H24 D7001是把十六进制 24 送进控制区里的“寄存器地址”槽位MOV K2 D7002是把十进制 2 送进“点数”槽位。模块执行时会把这两个值按协议表的规则编码进请求帧的第 3 到第 6 字节剩下的从站地址、功能码、CRC 由协议表自己生成。这就是“预编译协议”的实际含义你填的是语义参数读谁、从哪读、读几个模块负责把它翻译成字节。你不需要知道 CRC 多项式是 0xA001也不需要知道帧尾要静默 4ms这些都在协议表里。但这不意味着可以随便填。点数的上限受模块接收缓冲大小限制一次读几百个寄存器会直接超时寄存器地址跨功能码边界比如把线圈地址当保持寄存器填会收到异常响应功能码最高位被置 1返回 83。遇到这种情况异常代码通常会在控制区里留一份取出来对照手册看是哪一类错误比在梯形图里瞎猜快得多。4.3 多从站轮询一条 485 总线的排队与时间预算两线制 RS-485 是半双工总线同一时刻只能有一个主站发问、一个从站回答。这意味着不管总线上挂了多少台设备必须排队。程序里通常用步进节拍M 区一组状态位串起来第 1 步发站号 1 的参数、启动等完成位完成位 ON 后取值、复位、进第 2 步一轮跑完回到第 1 步。排队之前先算一笔时间账能省掉很多“为什么通信跟不上”的困惑def txn_ms(baud9600, req_bytes8, resp_bytes9, slave_delay_ms15): 估算一次 MODBUS 事务的耗时8 数据位 偶校验 1 停止位 11 位/字符 char_ms 11 * 1000 / baud # 每字符耗时9600 下约 1.15ms send_ms req_bytes * char_ms # 请求帧发送时间8 字节约 9.2ms recv_ms resp_bytes * char_ms # 响应帧接收时间9 字节约 10.3ms frame_gap 3.5 * char_ms # 两帧之间的静默期约 4.0ms return send_ms recv_ms frame_gap slave_delay_ms print(round(txn_ms(), 1), ms) # 单次事务约 38.5ms print(round(txn_ms() * 8, 1), ms) # 8 个从站轮一轮约 308ms这个函数里没有一项是随便加的char_ms由串口参数决定改波特率它跟着变slave_delay_ms是从站的处理延迟变频器一般在 5~20ms老设备可能更久frame_gap就是前面说的 3.5 字符静默。按 8 个从站算下来一轮要 300ms 左右——如果你的工艺要求 100ms 刷新一次那不是程序写得不好是物理上排不下只能提高波特率、减少点数、或者拆成两条总线。把这段账算清楚再看轮询逻辑就很明确了相邻两帧之间必须有间隔间隔可以靠“完成位 ON 之后延迟几个扫描周期再启动下一次”这种笨办法实现也可以靠定时器节拍。用笨办法时注意延时太短会让从站还在处理上一帧时就收到新请求表现为偶发无响应。5. 避坑与排查五个让通信“看着通其实不通”的坑5.1 回波没关模块把自己发的帧收下了现象通信时好时坏CRC 错误率不固定抓包能看到一堆看不出规律的错帧有时候前几个字节是对的后面全乱。原因两线制 RS-485 是半双工模块在发送时它自己发出去的差分电平会同时进到自己的接收器里。如果模块没屏蔽这段回波它就会把自己刚发的请求帧当成从站的回帧收下来两段字节在接收缓冲里错位后面的解析自然全乱。解决把回波传送关掉。示例里的做法是TOP H90 H1C2 K1 K1直接给缓冲存储器对应位置 1。这一步要在通信开始前做一次放在初始化段里别放在轮询循环里反复写。5.2 XY 地址填错指令跑去访问别的模块现象G.CPRTCL一执行就异常结束或者执行完不报错但一点数据都没有换个插槽以后现象又变了。原因工程里给智能功能模块填的起始 XY 地址和实际插槽不一致。Un 本质上是 I/O 编号填错了指令就去访问那个编号上的另一个模块参数被写到别人的缓冲存储器里。解决先在硬件组态里确认起始 XY 地址再核对G.CPRTCL的 Un 和缓冲区操作指令的操作数两处必须一致。改插槽、加模块之后这一段要重新过一遍不能想当然。5.3 协议没写进模块程序没问题模块里是空的现象程序是对的参数也是对的但G.CPRTCL一跑就异常用监视功能看模块里根本没有那条协议的编号。原因只做了程序下载没做 在线 → 模块写入。协议表存在 GX Works2 的工程里不写进模块等于没配置。解决每次改完协议表走一次模块写入并确认弹出的是写入成功提示、选中的是正确模块。现场有多台设备时尤其要看清目标写错模块会把另一个通道的配置覆盖掉而且覆盖完不会有任何警告。5.4 串口参数不一致连接正常全是超时现象接线查了三遍、地址也确认过就是收不到回帧偶尔能收到一次但数据不对。原因波特率、数据位、校验位、停止位这四项必须和从站完全一致。PL 侧设了偶校验、从站是 None或者一边 9600 一边 19200从站收到的就是一串垃圾自然不回话。解决先按从站的手册把四项抄下来再去 CH2 的设置里逐项核对。核对顺序建议从波特率开始波特率错了连字符都切不准后面几项对了也没用。改完参数记得重新写模块。5.5 同一扫描周期取结果永远拿到上一次的数据现象通信明明是通的读回来的频率总是滞后一个周期第一次读到的全是零轮询越快数据越像没更新。原因G.CPRTCL是异步指令启动条件 ON 的那个扫描周期里数据还没回来。在同一段逻辑里紧接着取 D 区数据拿到的必然是上一次的结果或者初始值。解决把取数逻辑挂在完成位 (D) 的上升沿之后取完再复位启动条件。同时确认启动条件是脉冲而非常 ON——常 ON 会让模块在完成之后立刻又启动总线被占满反而拖慢整体轮询。6. 设计取舍预编译协议这件事今天还值不值得学把这套东西拆完会发现它的核心取舍只有一句话把“时序敏感的协议状态机”和“时序不敏感的工艺逻辑”拆到两个处理器上。模块那颗芯片只干一件事——按 3.5 字符的规矩收发字节CPU 只干一件事——什么时候发起、拿到结果怎么用。代价也很清楚换协议要重新走一遍模块写入参数和软元件的对应关系依赖工具导出可读性远不如一段自解释的代码。横向比一下三种做法取舍就更明白了方案帧与 CRC 谁算时序谁保证换协议要动什么模块内置协议表模块硬件定时器改协议表 模块写入无顺序协议自己拼帧CPUCPU 扫描 定时器改梯形图逻辑外挂网关 / 串口服务器网关网关改网关配置第一种在实时性上最稳第二种最灵活但最容易在时序上翻车第三种最省事但多了一个可能没人管的黑匣子。我做过的现场里最怕的不是技术选型而是选完之后没人知道边界在哪——网关配错了找不到人改梯形图拼帧的定时器被后来人“优化”掉了模块里的协议表被覆盖了没人发现。所以验证这件事的方法也得跟着拆。协议这一层用工具的通信状态监视看发送字节数、接收字节数和错误代码能直接判断是“没发出去”还是“发出去没回来”电气这一层用串口分析仪或者在 A/B 线上挂个示波器看差分电平和帧间隔是判断终端电阻、屏蔽接地、共地这类问题的后悔药。两层都过一遍剩下的基本就是参数映射问题查手册能解决。从那以后我每次接手这类通信不管是第一次上电还是别人交接过来的老工程都强制走一遍这个顺序先确认模块里有没有协议表再核对 Un 和起始 XY再抓一次真实报文最后才动程序逻辑。跳过前三步直接改梯形图十有八九是在给一个没配好的模块改代码。希望帮到你。本文还有配套的精品资源点击获取