ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Modbus RTU读寄存器耗时全解析:从帧结构到轮询周期估算

Modbus RTU读寄存器耗时全解析:从帧结构到轮询周期估算 做Modbus RTU上位机或者PLC通讯调试的人基本都遇到过这种场面RS485线也接对了从站地址没冲突波特率两边也设成一样了结果上位机轮询还是时不时报超时或者总感觉刷新慢。前阵子我调一套采集终端上位机计划轮询三十几台表计按我最初估计1秒转一圈绰绰有余结果现场就是噼里啪啦超时。后来我把Modbus Poll抓下来的报文逐条对了一遍又蹲在工位上拿计算器把每个字节的时间重新捋了一遍才发现问题根本不在硬件而是我对于“Modbus RTU在RS485上读寄存器到底要花多少时间”这件事心里一直没一本清楚账。这篇就把这笔账从头到尾算清楚从帧结构到波特率从3.5字符间隔到从站处理时间全部掰开揉碎。这篇文章适合谁看做上位机开发、PLC和变频器通讯调试、嵌入式采集终端设计、SCADA系统集成的工程师都可以对照这个计算方法去估算自己的轮询周期排查超时和响应慢的问题。哪怕你只是用Modbus Poll这类工具偶尔抓包把这套理论搞明白之后你再看到抓包软件上的时间戳就会真正知道每一毫秒都花在哪里了。1. 先把账目列出来一次读寄存器到底花在哪些地方1.1 一个事务的四段时间Modbus RTU是半双工的工作模式主站发请求从站应答总线上一来一回才完成一次读操作。所以一次读寄存器的完整耗时不是简单算一帧报文的时间而是由四段组成的主站发送请求帧的时间从站收到请求后的处理时间从站发送响应帧的时间帧与帧之间必须保留的静默间隔这四段时间里第一段和第三段是纯传输时间由波特率、帧长度决定第二段是从站的固件处理速度决定的和设备性能强相关第四段是Modbus RTU协议规范要求的最短空闲时间。我在现场踩过的坑是很多工程师只算了“发出去的8个字节要多久”然后拿这个数值去设超时和轮询周期结果现场一跑就废。因为响应帧通常比请求帧长算漏了响应帧时间整个估算就偏了一倍以上。1.2 为什么不能简单用“帧字节数除以波特率”有人说那不就是字节数除以波特率嘛。9600波特率1秒传9600个bit8个字节就是64个bit那不就是6.7毫秒这么算是错的原因有两个。第一串口传输一个字节线上实际走的不只是8个数据位。起始位、停止位如果有校验位还得加一位这些全部要在波特率里一起算。所以8N1格式下一个字节是10个bit8E1或者8O1格式下是11个bit你只算8个bit直接就少了20%。第二请求帧和响应帧的字节数不一样尤其读寄存器响应帧长度是跟寄存器数量线性增长的。你要是读100个寄存器响应帧长度两百多字节这部分时间比请求帧大了好几倍不算进去等于白算。所以准确的做法是先把“一个字节在线路上要占多长时间”这个基本单位算清楚再分别算出请求帧和响应帧各占多少时间最后加从站处理时间和帧间静默。这也是下面每一节做的事。2. 字节时间、帧结构与3.5字符间隔2.1 串口上一个字节的传输时间先明确一个基础概念。RS485的物理层本质上就是UART串口Modbus RTU跑在UART之上。UART发送一个字节一帧的格式是1位起始位低电平有效告诉接收方“开始接收”数据位常见的是8位也有7位的老设备校验位可选无校验N、偶校验E、奇校验O停止位常见1位或者2位高电平表示这一字节结束所以一个字节在线路上占用的总bit数不是固定8而是总位数 1(起始位) 数据位数 校验位数 停止位数Modbus RTU最常见的配置是8N1也就是8数据位、无校验、1停止位那一字节就是1801等于10个bit。8E1是1811等于11个bit。每字节耗时等于总位数除以波特率。以9600波特率、8N1为例每字节耗时 10 / 9600 ≈ 1.0417 ms我习惯在脑子里把这个值记成“9600下1毫秒多一点”。19200波特率就是0.52毫秒左右115200波特率就是0.087毫秒左右。这个基本单位是整个计算的地基后面所有时间都得乘它。2.2 03功能码的请求帧与响应帧Modbus RTU报文结构很简单没有固定的起始结束字符就是一串字节。读保持寄存器用03功能码读输入寄存器用04功能码这两者的帧结构一样。03功能码请求帧主站发给从站一共8个字节字段长度说明从站地址1字节1到247功能码1字节0x03起始寄存器地址2字节高字节在前寄存器数量2字节高字节在前1到125CRC校验2字节低字节在前所以无论你读1个寄存器还是读100个寄存器请求帧都是固定的8个字节。这一点很多入门的人会记错以为读的数据多请求帧也会变长其实不会变长的是响应帧。03功能码响应帧从站回复给主站字段长度说明从站地址1字节和请求帧一致功能码1字节0x03最高位置1表示异常字节数1字节寄存器数量×2寄存器数据N×2字节每寄存器2字节高字节在前CRC校验2字节低字节在前假设读N个寄存器响应帧长度就是 5 2N 个字节。读10个寄存器响应帧就是25个字节读100个寄存器响应帧就是205个字节。注意这个长度是线性的寄存器数量翻倍响应时间基本也翻倍。顺带提一句01功能码读线圈、02功能码读离散输入它们的响应帧数据是按位打包的字节数是向上取整的N除以8响应帧长度是5加N除以8的向上取整。所以同样数量的点位读线圈比读寄存器省时间。2.3 帧间静默为什么Modbus RTU能靠“沉默”分帧Modbus RTU报文没有起始字符也没有结束字符那接收方怎么知道一帧从哪里开始、到哪里结束答案是靠时间间隔。协议规定一个报文内部的相邻字符间隔不能超过1.5个字符时间如果超过了接收方就认为这一帧不完整直接丢弃。而两个独立报文之间必须保持至少3.5个字符时间的静默。也就是说主站发完请求帧之后至少要沉默3.5个字符时间从站才能开始回响应帧。这个3.5字符时间在实际计算里不能省。以9600波特率8N1为例3.5字符时间 3.5 × 1.0417 ≈ 3.65 ms不要小看这3.65毫秒在一个轮询周期里每个从站都要付出这么一段静默时间。现场设备多了以后光帧间间隔累加起来就是几百毫秒的量级。很多从站固件实现里会在这个3.5字符时间的基础上再加一点余量或者干脆固定延时几毫秒再响应。这也是为什么理论值和实测值经常有出入的原因之一后面第5节会细说。3. 手把手算一遍9600bps读10个寄存器耗时全过程3.1 先定参数再算字节时间现在我们把所有参数定下来完整算一遍。场景是主站用9600波特率、8N1格式通过RS485向一个从站发送03功能码请求读取10个保持寄存器。先算基本单位每字节耗时 10 / 9600 ≈ 1.0417 ms 3.5字符时间 3.5 × 1.0417 ≈ 3.65 ms这两个数是下面所有计算的基础。如果你用了8E1格式那就是11除以波特率每字节时间会多10%总体耗时也相应增加。设计系统时如果对实时性敏感尽量统一用8N1省出来的都是实打实的时间。3.2 请求帧和响应帧各占多少时间读10个寄存器请求帧是8个字节响应帧是5加2乘以10等于25个字节。分别计算请求帧耗时 8 × 1.0417 ≈ 8.33 ms 响应帧耗时 25 × 1.0417 ≈ 26.04 ms这就很直观了读10个寄存器从站在总线上发送响应数据的时间是主站发请求时间的三倍多。很多人估算超时只按请求帧算那一定是算不准的。算到这里主站发完请求到从站响应帧发完这个纯传输时间就是8.33加26.04大概34.4毫秒。但我们还没算从站处理时间和帧间静默。3.3 从站处理时间与RS485收发切换从站收到请求帧之后要先解析地址、功能码、CRC校验再去读取对应寄存器的值组装响应帧最后把数据放到发送缓冲器里发出去。这个过程消耗的时间就是从站处理时间。从站处理时间是多少完全取决于硬件。有些高速模块能做到1到2毫秒处理完很多国产仪表、变频器、温控器要10到20毫秒甚至有些老设备会固定延时几十毫秒。我在现场见过一个温度变送器固件里写死了响应前延时100毫秒这种设备一接进来整个轮询周期直接垮掉。除了从站处理时间还有一类隐藏开销也在这个阶段RS485收发切换时间。RS485是半双工总线主站发完请求后要把收发器的方向从发送切换到接收从站回完帧后也要做一次方向切换。使用自动收发电路时这个切换过程由硬件完成但通常也要几十到几百微秒。虽然单次不大在高速轮询大量从站时累积起来就不能忽略了。综合以上我建议在计算时给从站处理时间加一个经验值。如果你不了解具体从站芯片性能可以从10毫秒起步估算。于是这一段的完整公式就是单事务耗时 请求帧耗时 3.5字符时间 从站处理时间 响应帧耗时套进我们现在的数值单事务耗时 8.33 3.65 10 26.04 ≈ 48.02 ms也就是说9600波特率下读10个寄存器一次完整事务大约48毫秒。如果上位机用默认的1000毫秒超时设置这个值看起来绰绰有余但如果你的系统里要轮询几十个从站问题就开始显现了。3.4 引申32台变频器的轮询周期估算这里正好接一个网上经常有人问的场景一个PLC要和32台变频器做Modbus RTU通讯到底可不可行我用上面的方法算一下你就知道怎么判断了。假设每台变频器只读2个寄存器比如运行状态和当前频率。请求帧还是8个字节响应帧是5加2乘2等于9个字节。9600波特率8N1下请求帧耗时 8.33 ms 响应帧耗时 9 × 1.0417 ≈ 9.38 ms 3.5字符时间 ≈ 3.65 ms 从站处理时间变频器通常偏慢取20 ms 单事务耗时 ≈ 8.33 3.65 20 9.38 ≈ 41.36 ms 轮询32台 41.36 × 32 ≈ 1323 ms所以这个配置下完整轮询32台变频器至少是1.3秒一轮。如果你的控制逻辑要求500毫秒内刷新一轮那就直接不行。要么提高波特率到19200或者38400要么减少每台变频器的读取数量要么把从站分组到多个串口。如果波特率提到19200各项传输时间减半但20毫秒从站处理时间不变单事务大约变成30.5毫秒32台约0.98秒。注意即使波特率翻倍总耗时并没有减半这就是下一节要展开的重点波特率提升的边际收益是递减的。4. 波特率与耗时一个值得记住的边际收益结论4.1 不同波特率下的完整对比继续用读10个寄存器、从站处理时间10毫秒这个场景把常见的几个波特率全部算一遍放在一起对比波特率每字节耗时请求帧8字节响应帧25字节3.5字符间隔从站处理单事务总耗时24004.167 ms33.3 ms104.2 ms14.6 ms10 ms162.1 ms48002.083 ms16.7 ms52.1 ms7.3 ms10 ms86.1 ms96001.042 ms8.3 ms26.0 ms3.6 ms10 ms48.0 ms192000.521 ms4.2 ms13.0 ms1.8 ms10 ms29.0 ms384000.260 ms2.1 ms6.5 ms0.9 ms10 ms19.5 ms1152000.087 ms0.7 ms2.2 ms0.3 ms10 ms13.2 ms这张表信息量很大。从2400提到9600单事务从162毫秒降到48毫秒省了114毫秒效果非常明显。但从9600提到19200只省了19毫秒从19200提到38400只省了9.5毫秒从38400提到115200只省了6.3毫秒。这就是边际收益递减波特率越高传输时间在总耗时里占比越小你再怎么提高波特率收益都有限因为从站处理时间这个“固定成本”一直压在那里。4.2 从站处理时间为何成为瓶颈这个现象背后的逻辑很简单。当波特率足够高以后总线上的传输时间已经短到可以忽略真正决定一次事务耗时的是从站内部从“收到请求”到“发出响应”的处理时间。你按115200波特率算读10个寄存器传输相关的总时间才3毫秒出头但从站处理就要10毫秒占了大头。所以你在设计系统时如果发现轮询周期卡在某个数值上不去不要一门心思只想到提高波特率。先查从站响应延迟是多少。很多设备手册里会写“响应时间小于多少毫秒”这个参数有时候比波特率更重要。我自己的经验是真要压榨轮询周期优先做三件事第一把不重要的从站放到低速轮询组里去用更长的周期慢慢扫第二把连续可读的寄存器一次读回来减少事务次数第三对实时性要求高的点位单独用高速总线或者干脆换Modbus TCP。提高波特率只排在第四位。还有一个细节高速率下RS485线缆质量、终端电阻、分支长度的影响会放大。9600波特率下可能怎么接都能跑上了115200之后线缆稍微乱一点误码率就上来了反而频繁重试总耗时比9600还高。所以选波特率不是越高越好要看现场布线条件和从站支持情况。5. 理论计算与实测的差距工具验证和排查思路5.1 用Modbus Poll验证理论值算得再准最终要和实际对账。我自己最常用的验证工具就是Modbus Poll它可以直接发请求并且在界面上显示从发送请求到接收到响应的时间差。你可以在软件里设置好串口号、波特率、数据格式然后填入从站地址和功能码连上之后观察响应时间。具体操作是在Modbus Poll里先设置Connection选好COM口和波特率比如9600 8N1然后新建一个Read Definition选03功能码填起始地址和寄存器数量。点连接后软件会周期性地发送请求帧。你重点看界面上的响应时间或者用抓包工具看每一帧的时间戳。我实测读10个寄存器9600波特率下Modbus Poll显示的响应时间通常在45到60毫秒之间和理论值48毫秒对得上。但如果你的数值比理论值大了很多就要按下面的原因去排查。5.2 实测耗时偏大的常见原因理论上算出来48毫秒实测却到了100多毫秒这种情况我遇到过不少次。原因主要有这么几类。第一上位机调度延迟。你的电脑操作系统、串口驱动、USB转串口芯片的驱动都会引入额外延迟。Windows下USB转RS485线一帧数据从应用层到物理总线经常有1到5毫秒的延迟。波特率越低这个延迟占比越小波特率越高占比越明显。第二从站响应里还有隐藏延时。很多仪表固件里收到请求后不是立即回帧而是有一个固定延时或者随机延时。我前面提到的那个固定延时100毫秒的温度变送器就是典型案例。这种设备你从Modbus Poll里看响应时间会发现有一个稳定的底数怎么调波特率都消不掉。第三RTU和上位机软件的不合理间帧处理。有些上位机软件实现的Modbus RTU不太规范发送完一帧之后额外等待了一个很长的帧间延时比如100毫秒。这种问题只能换软件或者改软件配置和物理链路没关系。第四总线质量问题引发的重发。线缆过长、没有终端电阻、收发器阻抗不匹配都会导致帧校验CRC错误从站不回帧或者主站丢弃响应然后超时重发。一次重发耗时就翻倍。遇到这种情况先用示波器看波形再检查RS485的A、B线接法和终端电阻。5.3 超时时间的合理设置超时时间设多大也是一个和理论计算直接相关的问题。设太短从站稍微慢一点就误报超时设太长一旦某个从站坏了不响应整个轮询周期被拖死。我的经验是把超时时间设置为“理论单事务耗时的2到3倍”。以9600读10个寄存器为例理论48毫秒那超时设150毫秒左右就合适。如果你轮询32个从站其中有一个从站坏了150毫秒的超时只会拖慢4到5秒的总周期还在可接受范围。如果某个从站偶尔慢但功能正常你可以在Modbus Poll里单独测一下它的最大响应时间然后给这个从站单独设置超时。千万不要图省事把所有设备都用同一个1000毫秒超时那样一旦有个别从站离线后面排队的设备全部要跟着等系统响应会变得非常迟钝。另外Modbus Poll这类工具本身也有个特点值得注意如果你在工具里设置了很长的轮询间隔那么软件的“周期时间”会包含你的轮询间隔这时候看到的单帧耗时就不是纯传输时间了。所以拿它验证理论计算时要把轮询间隔设成0只看实际响应时间差。6. 附Python快速计算脚本改参数就能用最后把我自己常用的一个计算脚本贴出来。它的逻辑就是这篇文章里的公式参数直接写在代码开头改一改就能用在你自己项目里。我每次估算新项目轮询周期时都跑一下比按计算器省事。# Modbus RTU RS485 读寄存器耗时理论计算 # 按需求修改以下参数 baudrate 9600 # 波特率 data_bits 8 # 数据位 stop_bits 1 # 停止位 parity N # 校验位: N / E / O register_count 10 # 请求读取的寄存器数量 func_read_bit False # True表示按位读取线圈(01/02)False表示读寄存器(03/04) handle_time_ms 10 # 从站处理时间单位ms slave_count 32 # 轮询的从站数量 extra_guard_ms 0 # 额外余量比如上位机调度延迟单位ms # 计算每字符比特数 if parity N: bits_per_char 1 data_bits stop_bits else: bits_per_char 1 data_bits 1 stop_bits # 每字节传输时间单位ms t_char_ms bits_per_char / baudrate * 1000 # 帧长度计算 if func_read_bit: req_len 8 res_len 5 (register_count 7) // 8 else: req_len 8 res_len 5 register_count * 2 t_req_ms req_len * t_char_ms t_res_ms res_len * t_char_ms t_interval_ms 3.5 * t_char_ms # 单事务总耗时 t_single_ms t_req_ms t_interval_ms handle_time_ms t_res_ms extra_guard_ms # 轮询周期 t_poll_ms t_single_ms * slave_count print(f每字节传输时间: {t_char_ms:.2f} ms) print(f请求帧长度: {req_len} 字节, 耗时: {t_req_ms:.2f} ms) print(f响应帧长度: {res_len} 字节, 耗时: {t_res_ms:.2f} ms) print(f3.5字符间隔: {t_interval_ms:.2f} ms) print(f从站处理时间: {handle_time_ms:.2f} ms) print(f单事务总耗时: {t_single_ms:.2f} ms) print(f轮询 {slave_count} 个从站: {t_poll_ms:.2f} ms ({t_poll_ms/1000:.2f} s))这个脚本跑出来和文章里的计算完全一致。读10个寄存器、9600波特率、32个从站最后会显示单事务48毫秒左右整轮1536毫秒左右。有一点我必须强调这套理论计算的作用是给你一个“可预期的基准线”。到了现场务必用Modbus Poll或者示波器实测一次因为你永远不知道某个从站固件里藏着什么延时逻辑。实测数据和理论值对上了你后续设超时、定轮询周期、判断故障心里就都有数了。再分享一个实战技巧面对多个从站的系统我习惯先计算理论轮询周期然后在Modbus Poll里把每个从站单独测一遍响应时间把明显偏慢的设备挑出来。有的设备慢是因为固件性能差有的设备慢是因为它同时被好几个主站轮询总线冲突导致退避重发。这两种情况处理方式完全不同前者只能换设备或者少读数据后者要去排查总线上的地址冲突和多主站问题。时间管理这件事在Modbus RTU这条总线上说到底就是把这本时间账算明白再拿实测去印证它。
返回列表