ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PLC编程核心:进制转换与数据类型实战解析

PLC编程核心:进制转换与数据类型实战解析 很多做PLC开发的工程师前期学习梯形图和指令表都很顺利真正开始做项目后却经常卡在现场联调上设备通讯不上、触摸屏显示乱码、模拟量数值对不上、状态字读出来像天书。这些问题的背后往往不是程序逻辑写错了而是进制转换和数据类型这两个基础概念没有真正对齐。有经验的工程师处理这类问题不是靠瞎猜而是有一套固定的排查思路先看数据是什么类型再看寄存器是多少位最后看进制对不对。这恰恰是PLC编程里面最容易忽略、也最值得系统梳理的部分。本文会从进制和类型的基本概念讲起结合模拟量转换、Modbus通讯解析这些真实场景把知识落到可以直接操作的方法上。读完你就能理解为什么0x41A0被解析成20.0而不是别的数字为什么同样的16位数据用有符号和无符号去读会差65536以及模拟量为什么总出现27648这种奇怪数值。1. 为什么PLC编程绕不开进制转换和数据类型先看几个实际项目中反复出现的现象。第一类现象是值域问题。通讯读取回来的保持寄存器明明设备端写的是0到100的数值触摸屏上却突然跳到65535甚至负数。这个问题的本质是Modbus保持寄存器只有16位而16位数据可以解释为无符号整数0到65535、有符号整数-32768到32767或者其他类型。上位机和PLC只要有一边类型定义错了数值马上错乱。第二类现象是地址问题。三菱FX系列PLC的输入继电器X和输出继电器Y采用八进制编号。也就是说地址顺序是X0、X1……X7、X10、X11跳过了X8和X9。很多新手习惯用十进制思维去分配点位排到X7之后直接写X8现场接线却找不到这个点。这个问题看似简单但只要品牌换一换类似的现象会在不同设备上反复出现。第三类现象是解析问题。从变频器、仪表或第三方设备读取数据时返回的寄存器值往往不是直接能用的工程量。比如读取一块压力变送器的4-20mA信号PLC内部得到的是0到27648的原始值需要线性映射成0到1.6MPa才能在画面显示。如果直接用原始值去显示用户看到的是一串没有意义的数字。这三个现象分别对应进制认知、数据类型认知和量程映射。本文要做的就是把这几个容易被忽视的知识点串起来。同时声明一个判断进制转换和数据类型不是PLC入门阶段的纯理论内容而是现场调试时的核心工具。越早建立这套知识框架后面做复杂设备通讯、模拟量闭环控制、多品牌PLC联调时就越不容易被莫名其妙的数据错误卡住。2. 进制到底是什么PLC里的二进制、八进制、十六进制2.1 从继电器电路看“为什么是二进制”PLC硬件层面对“0”和“1”的依赖是天然存在的。继电器只有“得电”和“失电”两种状态晶体管输出也只有“导通”和“截止”两种状态。所以PLC内部所有数据最终都必须用二进制来表达。但开发者在监控软件里很少直接看一串长二进制因为可读性太差。比如一个16位的状态字用二进制表示可能是1010 1110 0000 1101让工程师去记这一串容易出错。于是有了十六进制每一位十六进制数字正好对应四位二进制。上面的数据可以写成0xAE0D这样长度的数据在PLC监控软件里看起来就清爽很多。这也是为什么PLC的程序监控、寄存器查看、通讯报文解析到处都在用十六进制。2.2 四种进制的对照表开发过程中最常用的是二进制BIN、八进制OCT、十进制DEC和十六进制HEX。先看一张0到15的对照表十进制二进制八进制十六进制00000001000111200102230011334010044501015560110667011177810001089100111910101012A11101113B12110014C13110115D14111016E15111117F这张表看起来简单但它是后面所有排查工作的基础。一个16位数据的每一个bit位都代表一个具体状态而用十六进制表示时每四位一组对应一个十六进制字符非常规整。2.3 八进制陷阱三菱FX的X/Y地址为什么没有8和9很多教材在讲三菱FX系列时会提醒“X和Y按八进制编号”但不少初学者仍然不理解为什么要这样。如果你正在用三菱FX系列PLC打开软件分配输入点时会看到PLC的输入端子地址是从X0开始到X7之后并不是X8而是X10。同理Y0到Y7之后是Y10没有Y8和Y9。这不仅影响IO分配还会影响一些位软元件的编号习惯。这个问题在现场接线时最明显图纸上如果按十进制习惯标了X8而PLC端子上根本没有X8这个地址就会导致接线表错误、程序里找不到点、调试时反复核对。类似的存在八进制编号习惯的PLC系列并不少所以在做点位表之前先确认清楚这个品牌、这个系列的地址进位规则是非常必要的一步。2.4 十六进制与字节、字的关系数据在PLC中按长度分为位、字节、字、双字等层级进制和这些层级的关系是1个十六进制位等于4个二进制位正好是一个半字节。2个十六进制位等于8个二进制位也就是1个字节BYTE。4个十六进制位等于16个二进制位也就是1个字WORD。举个例子寄存器D100中保存的数值如果是十六进制的0x3F2A那么它占用1个字的长度。其中高字节是0x3F低字节是0x2A。在分析通讯报文、或者把两个字节拼成一个字的时候这种拆分能力很关键。3. 进制转换的实用方法与速算技巧3.1 按位权展开法进制转换不是必须用计算器掌握位权展开法之后几个常用范围完全可以心算。以十六进制转十进制为例0x2F 可以拆成2 × 16^1 32F × 16^0 15所以0x2F 32 15 47。十进制转二进制也可以反过来做。比如十进制数值85可以拆成64 16 4 1对应的二进制位是64 32 16 8 4 2 1 1 0 1 0 1 0 1所以85 0b01010101。3.2 8421速算法在现场调试时最常用的是四位二进制和一位十六进制的互转。四位二进制的权值从高到低分别是8、4、2、1。例如二进制1101拆成1×8 1×4 0×2 1×1 13所以1101对应十六进制的D。反过来十六进制0x9B拆成两个四位组9 1001B 1011所以0x9B 0b10011011。这个换算足够处理绝大多数状态解析、数据查看和通讯报文分析工作。3.3 用脚本验证换算结果如果怕心算出错可以用一段简单Python脚本现场验证。在电脑上跑一下不会依赖PLC环境适合平时学习和换算出错时排查。# 进制转换验证脚本 def show_conversion(value): print(f十进制: {value}) print(f二进制: {bin(value)}) print(f八进制: {oct(value)}) print(f十六进制: {hex(value)}) # 以十进制85为例 show_conversion(85) # 也可以从十六进制字符串转换为十进制 hex_str 2F dec_value int(hex_str, 16) print(f0x{hex_str} {dec_value}) # 从一个16位寄存器数据的位角度观察 word_value 0xAE0D print(fbit0 {word_value 1}) print(fbit4 {(word_value 4) 1}) print(fbit15 {(word_value 15) 1})这段脚本输出的结果可以帮助你对照PLC监控软件里的数据。不要嫌它简单真到设备联调时用脚本和计算器验证一个寄存器值比在PLC里反复改程序快得多。4. PLC数据类型一次把BOOL/BYTE/WORD/DWORD/INT/REAL说清楚4.1 从位到双字数据长度的层级关系PLC中的数据类型本质上是在描述“一段数据占多大空间、按什么规则解释”。以西门子为代表的IEC 61131-3体系、以及三菱、欧姆龙、基恩士等常见PLC都会定义类似下面的基础类型类型长度取值范围典型用途BOOL1位TRUE / FALSE按钮、开关、运行状态BYTE8位0~255单字节数据、ASCII码WORD16位0~65535寄存器原始值、字数据DWORD32位0~429496729532位无符号数据、BCD码INT16位-32768~32767普通整数、模拟量原始值DINT32位-2147483648~2147483647长整数、累计值REAL32位约±3.4×10^38浮点数、工程量计算其中最容易混淆的是WORD和INT。两者都占16位但WORD按无符号解释INT按有符号解释。同一个原始数据0xB1E0如果按WORD看是45536按INT看则是-20000。很多通讯异常和触摸屏显示异常根源就在这里。如果看看C语言里对应的类型其实比较好理解WORD类似unsigned shortINT类似shortDWORD类似unsigned intDINT类似intREAL类似float。这样跨语言一想思路会清晰很多。4.2 实数REAL为什么32位也不一定精确REAL在PLC内部采用类似IEEE 754的标准存储32位数据由符号位、指数位和尾数位组合而成。它适合保存过程量比如温度、压力、流量但在做累加、计数、金额计算时并不安全可能出现精度丢失。工程上的建议是需要精确累计的数据用DINT需要连续变化的过程量用REAL。不要在一个计算链里混用INT和REAL否则每次转换都可能有隐患。4.3 类型转换显式转换而不是硬塞在ST语言或SCL中不同类型之间不能随便赋值。比如把REAL类型变量直接赋给INT类型变量很多编译器会直接报错。正确的做法是调用类型转换函数。以IEC 61131-3风格的ST语言为例VAR rawAd : INT; pressure : REAL; END_VAR // 读取模拟量原始值后转换为实数进行量程换算 pressure : INT_TO_REAL(rawAd) * 0.01;// 累加计数器避免用REAL VAR totalCount : DINT; eventValue : INT; END_VAR totalCount : totalCount DINT_TO_DINT(eventValue);在实际项目中建议把数据类型转换的代码集中写并加注释说明为什么要转换。这能减少后续维护人员踩坑的概率。5. 六个典型调试场景一眼定位进制和类型问题5.1 通讯值变成65535设备端通过Modbus上报一个线圈状态或者保持寄存器值时如果PLC端把寄存器类型定义成INT而设备端实际发送的是16位无符号数据两者之间就可能出现偏差。比如设备要上报的数值是45230这个值超过了32767。PLC端如果按INT解析结果会变成45230 - 65536 -20306。而触摸屏上定义的变量如果是无符号16位又可能显示为45230。不同客户端看到不同值很容易让现场进入“各说各话”的排查状态。遇到这种问题第一步是确认协议文档里定义的寄存器数据类型第二步是看PLC程序和上位机变量是否一致。5.2 同一个0xB1E0读出-20000还是45536很多第三方设备的手册会直接给出十六进制地址和数值范围。比如状态寄存器返回0xB1E0此时直接把0xB1E0当无符号数看是45536如果按有符号16位看是-20000。你没有改错程序只是解释方式不对。这种方式在协议文档里一旦写错会导致数据看起来“随机跳变”。所以当通讯数据的值域不在预期范围内时优先想想当前程序是用INT还是WORD读的这个寄存器5.3 触摸屏上的数据错位触摸屏的变量类型和PLC数据块中的变量类型如果不一致显示结果也会异常。比如PLC里DINT是32位触摸屏上却定义成16位INT那么在屏上看到的只会是低16位数据累计值一旦超过32767就变成负数。排查方法也很直接先看PLC在线监控中的变量值再看触摸屏变量类型两边对齐。尤其是在做多语言、多触摸屏项目时类型不一致问题几乎必然会遇到。5.4 模拟量读取值跳变或不对模拟量模块的原始值并不是一个固定的0到100或0到1000的“好看”数字而是模块A/D转换后的数字量。S7-1200/1500系列的模拟量通道常见的0~27648对应4~20mA也就是模块会输出0到27648的整数。这个数值需要经过标准化和缩放才能变成工程量。如果程序里直接把原始值当成工程量去显示或者漏掉了标准化步骤数值就会很大且没有实际意义。很多人因此误判为模块坏了实际上只是换算没做完整。5.5 状态字的按位解析设备的运行状态、报警信息常常打包在16位或32位的状态字中。比如一个状态字最高位表示故障bit4表示运行bit0表示远程模式。直接看十进制数是看不出这些信息的需要把状态字按位取出来。ST中常用的写法是IF (statusWord AND 16#0010) 16#0000 THEN // bit4为1设备处于运行状态 END_IF;这里的16#0010是十六进制常量对应二进制0000 0000 0001 0000即bit4。这个手法的核心就是进制和位操作的组合。5.6 Modbus浮点数的字节序和字序问题Modbus协议本身不规定浮点数如何编码。厂商既可能按大端模式存储也可能按小端模式存储。同样两个16位寄存器0x3F80和0x0000组合起来可能是1.0也可能是某个极小值取决于PLC/仪表端是“字序字节序”的哪种组合。所以解析第三方设备的浮点数时先做一件小事用固定值测试。让设备输出一个已知浮点数比如0.0或1.0然后看收到的寄存器值再去决定字节序和字序怎么排。这是最稳妥的办法。6. 综合实战一模拟量4-20mA到工程量的完整换算6.1 先确认模块的数字量范围模拟量输入模块的A/D转换范围因品牌和型号而异。同样是4-20mA有些PLC对应0~27648有些对应0~4000有些对应0~32000。查手册是第一优先级。从通用角度看假设模块原始值rawAd的范围是rawMin到rawMax对应的工程量范围是engMin到engMax则线性换算公式为eng (rawAd - rawMin) × (engMax - engMin) / (rawMax - rawMin) engMin很多标准算法库其实就是这个公式的封装。6.2 西门子S7-1200中的标准化与缩放在TIA Portal中可以调用NORM_X和SCALE_X指令完成这个映射。先对模拟量原始值做标准化得到0.0到1.0之间的实数再映射到工程量范围// NORM_X: 将INT转换为0.0~1.0的REAL #normValue : NORM_X(INT, MIN : 0, VALUE : #rawAd, MAX : 27648); // SCALE_X: 将0.0~1.0映射为工程量 #pressure : SCALE_X(REAL, MIN : 0.0, VALUE : #normValue, MAX : 1.6);这种写法把量程配置放在指令参数里调试时改量程很方便不用动公式结构。这里真正容易踩坑的地方是数据范围西门子标准模拟量模块的4-20mA通常对应0~27648但有些特殊模块、或组态中选择了超限报警原始值可能超过这个范围。所以做报警判断时不能只看工程量的上下限也要看原始值是否越界。6.3 三菱FX系列中自己写换算三菱FX系列没有NORM_X/SCALE_X这种标准功能块但可以用公式直接写。假设程序从模拟量模块读到的32位数据为D100低16位和D101高16位先合并成DINT再转换为REAL最后做线性映射。合并两个16位寄存器为32位数据的ST风格代码如下#rawDint : SHL(IN : DINT_TO_DWORD(#highWord), N : 16) OR DINT_TO_DWORD(#lowWord);由于不同型号三菱PLC的指令集会有差异实际使用时以你所用CPU支持的数据合并方式为准。整体换算思路是一样的先拿到原始数字量再按公式映射到工程量。6.4 用Python离线验证换算公式在还没有PLC环境时可以用Python先把算法验证一遍避免把错误公式写进PLC再反复下载调试def scale_signal(ad_raw, raw_min, raw_max, eng_min, eng_max): 模拟量原始值线性映射为工程量 scale (eng_max - eng_min) / (raw_max - raw_min) return (ad_raw - raw_min) * scale eng_min # 西门子S7-12004-20mA对应0~27648量程0~1.6MPa raw_value 13824 pressure scale_signal(raw_value, 0, 27648, 0.0, 1.6) print(f原始值 {raw_value} 对应压力: {pressure:.4f} MPa) # 三菱某模块0~32000对应-40~100度 raw_value 16000 temperature scale_signal(raw_value, 0, 32000, -40.0, 100.0) print(f原始值 {raw_value} 对应温度: {temperature:.2f} °C)把这个脚本放到调试电脑里随时可以用来反推“现场这个原始值到底应该显示成多少”比直接在PLC里试错效率高很多。7. 综合实战二Modbus通讯数据的解析与验证7.1 Modbus寄存器默认长度是多少Modbus是一个非常经典的通讯协议。协议中一个保持寄存器Holding Register的默认长度是16位也就是一个字。线圈和离散输入是1位。所以当有人说“Modbus数据类型长度”通常指的就是一个寄存器16位、一个线圈1位。这个16位长度决定了后续所有解析逻辑。如果设备要传送32位浮点数必须占用两个连续的16位寄存器要传送8位ASCII码就可能需要把两个字符拼到一个寄存器里。7.2 32位浮点数的组合规则读取两个连续寄存器后要把它们组合成一个32位浮点数。这里存在字节序和字序两种维度字序寄存器n是高16位寄存器n1是低16位反之亦然。字节序每个16位寄存器内部高字节在前还是低字节在前。工程上常见的是“大端模式”高字节在先高位寄存器在前。但设备厂商完全可以采用其他方式。调试时用固定值去验证比读文档更快。7.3 用Python解析Modbus寄存器数据为了验证组合规则可以把串口或TCP调试工具抓到的原始寄存器值拿来离线解析。比如设备回传两个寄存器值是0x41A0和0x0000按大端字序组合成0x41A00000对应浮点数20.0。import struct def parse_modbus_float(reg_high, reg_low, word_orderbig): 解析两个16位寄存器为32位浮点数 if word_order big: raw (reg_high 16) | reg_low else: raw (reg_low 16) | reg_high return struct.unpack(f, raw.to_bytes(4, big))[0] # 模拟读到的两个寄存器 reg1 0x41A0 # 16672 reg2 0x0000 # 0 print(大端字序解析结果:, parse_modbus_float(reg1, reg2, big)) print(小端字序解析结果:, parse_modbus_float(reg1, reg2, little))输出可以看到字序不同会导致解析结果完全不同。这解释了为什么很多通讯项目里PLC和仪表反复对不上本质就是“两边对字节序和字序的理解不一致”。再写一个把16位状态字拆成布尔位的脚本def parse_status_word(status): bits {} bit_names { 0: 远程模式, 1: 本地模式, 4: 运行中, 15: 故障报警, } for bit, name in bit_names.items(): bits[name] (status bit) 1 return bits status 0x8010 result parse_status_word(status) for name, value in result.items(): print(f{name}: {value})这个脚本输出的结果可以直接和PLC监控软件中的二进制视图对照。8. 常见问题排查表很多现场问题其实是同一个原因的不同表现。下表列出高频现象、可能原因、排查方式和解决方案建议收藏备用。问题现象可能原因排查方式解决方案触摸屏数值变成6553516位无符号回绕或通讯中断看PLC在线监控的原始值判断通讯状态确认变量类型数值超出32767变成负数无符号数据被当有符号解析检查寄存器类型是INT还是WORD统一按协议文档定义类型模拟量显示数字很大没有把原始值映射为工程量查看模拟量通道原始值范围使用标准化缩放指令或公式换算三菱FX地址找不到X8八进制编号规则理解错误查PLC输入端子编号按X0~X7、X10~X17的规则分配浮点数在触摸屏上乱码字节序或字序不匹配用已知固定值测试设备按设备实际字节序调整组合逻辑状态字解析困难直接看十进制而不是按位拆使用二进制/十六进制视图用AND和移位按位判断状态累计值超过范围跳变DINT/REAL用错检查变量类型和转换函数累计用DINT过程量用REAL设备通讯正常但值一直不变寄存器地址或功能码错误对比协议文档中寄存器地址核对地址映射和数据块偏移这张表的核心作用是提醒你遇到数据异常不要第一步去改程序先判断是进制问题、类型问题还是量程问题。定位准确后再动手效率会高很多。9. 最佳实践与工程建议9.1 建立点位表时同时定义数据类型、量程、缩放系数在做项目前把每个IO点、通讯变量、中间变量整理成表。表中必须包含变量名称、PLC地址、数据类型、原始值范围、工程量范围、缩放系数、单位、报警值。这样后续写程序、做触摸屏、联调时才能有统一口径。9.2 程序里避免隐式类型转换很多PLC编程软件在INT和REAL混用时并不会直接报错但会在后台做隐式转换。隐式转换容易掩盖问题建议在关键计算处显式调用转换函数并加注释。代码最终要给人维护的而不是只让机器“能跑”。9.3 模拟量模块的量程配置要跟程序一致硬件组态中的量程、模块DIP开关的量程设置、程序中的标准化参数这三者必须保持一致。最常见的问题是硬件组态里选了0~20mA程序里却按4~20mA的0~27648去缩放导致数据整体偏移。9.4 调试时同时开二进制和十六进制视图PLC监控软件一般都支持把变量显示为十进制、十六进制或二进制。解析状态字、报警字时直接切到二进制视图核对寄存器地址时用十六进制视图看实际工程量时再用十进制视图。多视图切换是现场调试的基本操作。9.5 多品牌设备通讯时统一数据词典PLC、触摸屏、变频器、仪表各自可能使用不同的大小端模式。建议在项目开始时定一份“数据词典”统一每个变量的类型、字节序、字序、缩放系数。通讯联调时先拿固定值互相验证再接入真实数据避免一上来就面对一堆不可解释的数值。10. 总结与下一步实践方向进制转换和数据类型属于PLC编程中看起来基础、实际影响极大的知识点。本文从二进制、八进制、十六进制的基本关系讲起分析了三菱FX系列X/Y八进制编号、INT和WORD的区别、REAL和DINT的使用边界并通过模拟量换算和Modbus浮点解析两个实战案例给出了可执行的验证方法。下一步建议你用自己手头的PLC做两个小实验第一在程序里定义一个16位寄存器分别用十进制的45536和十六进制的0xB1E0查看同一数据的显示差异第二连接一个模拟量输入或通过调试仿真工具把一个4-20mA信号按公式换算成工程量记录原始值和转换结果。这两个实验做完你对进制和类型的理解会从“知道”变成“会排查”。再往后可以进一步学习浮点数运算精度、PID调节中的实时数据处理、以及多设备通讯中的协议解析。无论往哪个方向深入先把今天这套进制和类型的基础打牢后面少走弯路。
返回列表