
开头这几年在欧姆龙CP1H上做模拟量采集我走过一段弯路每个通道的量程换算、滤波、报警都直接在梯形图里铺开写结果一台设备十来个通道程序膨胀到上千步现场改一次量程要翻好几页网络逐条找地址调试阶段每天都被自己写的代码折磨。后来我把整个模拟量处理流程重构成了一套模拟量FB功能块程序把AD值读取、断线判断、滤波、工程值换算、超量程报警全部封装进功能块里每个通道只留一个实例程序结构瞬间清爽调试效率也翻了倍。这篇文章就把这套FB的接口设计、核心ST代码、创建调用流程和现场调试时踩过的坑完整摊开给正在用CP1H做模拟量采集、又不想重复写梯形图的朋友一个可以直接抄作业的模板。1. 为什么我劝你别用梯形图“硬写”模拟量1.1 一个通道费三个网络重复代码堆积如山很多从开关量入门PLC的人第一次接触模拟量时都会延续“一个信号一个线圈”的思维。比如采集一路4~20mA的液位信号习惯性做法是先写一个模拟量输入字传送指令再写一个除法指令做百分比接着写一个乘法指令变工程量然后接一个比较指令做上限报警再复制两遍网络做下限报警和断线判断。一路下来七八个网络看着倒也不算多可设备上通常有六路、八路甚至十几路模拟量一套程序复制十几遍后面的人看代码直接头皮发麻。更麻烦的是这种写法把“报警阈值”“量程上下限”“滤波参数”这些可调项散落在几十个网络里。现场调试时操作工让你把上限从80%改到85%你得先搞清楚这个80%到底藏在哪个比较指令里改完还可能漏掉另一处一模一样的常数。我见过不止一次因为改漏了参数设备运行一段时间后才暴露出来的情况。1.2 量程改动牵一发动全身漏改一处全乱套模拟量处理的本质是“物理量→标准电信号→数字量→工程量”的线性映射。把电信号变成数字量这一步由模块硬件完成剩下的数字量到工程量换算很多人直接在梯形图里用固定常数写死。今天传感器量程是0~10米明天换成0~5米你就得把所有写死的除法系数、比较阈值、显示缩放全部翻出来重算一遍。稍微粗心一点显示数值和实际值对不上QCS审核时就是一条重大不符合项。FB功能块最直接的收益是“参数化”。量程上下限作为功能块的输入参数暴露在调用界面现场改量程只需要修改实例化的两个参数所有换算、报警、超量程判断自动跟着变。这是我在重构之后感受最深的一点。1.3 通道多了以后故障定位全靠猜梯形图平铺写模拟量还有一个隐藏痛点没有统一的报警出口。哪一路传感器断线了、哪一路超量程了单靠在线监控看无数个中间变量的状态效率极低。我曾经在一个改造项目中遇到四路温度同时跳变现场操作工根本不知道哪一路出了问题最后是我拿着万用表一路一路排查才定位到是热电偶断线。把断线判断、超量程判断集中到FB的输出接口之后每一路的报警状态都规范地输出到固定的报警地址HMI上做一张一目了然的报警页故障定位从“翻网络”变成“看画面”。这也是FB给项目带来的长期维护价值。2. 动手前的模拟量底账地址、量程、数据格式2.1 内置模拟量与扩展模块的I/O字分配写FB之前先把CP1H的模拟量“家底”搞清楚。CP1H本身有好几种型号其中带内置模拟量的本体比如型号里有XA的通常自带4路模拟量输入和2路模拟量输出这些通道在I/O表里占用连续的字地址。以常见的XA型本体为例模拟量输入字一般从CIO 2000附近开始分配4个输入通道占4个字模拟量输出则在CIO 2010附近占2个字。但请注意这只是常见情况的参考最可靠的做法是打开CX-P软件查看工程树里的I/O表和存储器分配里面会明确标注每个模拟量通道对应的地址。如果你用的是扩展模拟量单元比如CP1W-AD0414路输入或CP1W-DA0414路输出这些扩展单元的I/O字是在插入硬件之后由软件自动分配的。我在一个项目里配置过一块AD041加一块DA041地址并不是固定不变的插在哪个扩展槽位都会影响分配结果。所以我的习惯是每新建一个工程第一件事就是截一张I/O表地址图存到项目文档里后面写程序、查故障都用这一张图避免猜地址。2.2 0~6000的数据单位到底在说什么欧姆龙模拟量模块的分辨率很特殊满量程对应0~6000不是常见的0~4095也不是0~27648。这一点必须从设计之初就刻在脑子里。0~6000是什么意思就是无论你选择0~10V、1~5V还是4~20mA模块满量程时数字量都对应6000量程零位对应0。举个例子4~20mA液位传感器量程0~10米。当实际液位是5米时传感器输出12mA模拟量模块读取到的AD值大约是3000因为12mA正好在4~20mA的中间位置。这个等比例映射关系是整个换算逻辑的核心。很多刚接触欧姆龙的人会下意识按“65535去算或者按16位满量程去推算”最后结果总差一截根源就在于没有先确认模块的分辨率单位。这个6000的底账还有一层含义AD值可以直接当作百分比来用。AD值除以60就是百分比数值比如AD值3000对应50.0%。做HMI显示的时候有时候不需要真的算出工程量直接显示百分比就能满足工艺要求运算还更简单。2.3 工程值换算从AD值到液位、温度、压力无论你采集的是液位、温度、压力还是流量换算公式只有一个就是直线映射工程值 (AD值 / 6000) * (工程量上限 - 工程量下限) 工程量下限这个公式本质上是初中一次函数但现场写错的概率一点都不低。常见错误是漏掉加了量程下限或者把6000写成了60000再或者干脆把0~6000当成了百分比直接显示。我建议在FB内部统一使用REAL浮点运算避免整数运算的截断误差。以0~10米液位、AD值1500为例工程值 (1500 / 6000) * (10 - 0) 0 2.5米就这么简单一个式子用梯形图写需要好几步放到ST里一行搞定。3. FB功能块的完整骨架接口定义到ST核心代码3.1 功能块的接口输入、输出、实例参数怎么定接口设计是整套FB的重中之重。我在第一版里犯过的错误是接口塞得太满量程上下限、滤波系数、断线阈值、超量程幅度、采样周期、通道号全部做成输入结果每个实例调用画面密密麻麻反而失去了一目了然的优势。后来砍掉了通道号和采样周期这两个参数通道号由外层程序决定采样周期靠PLC扫描周期自然进场接口清爽了很多。最终定稿的输入输出如下输入参数AD原始值wADValueUINT、工程量下限rEngLowREAL、工程量上限rEngHighREAL、滤波系数rFilterREAL、断线判定阈值rBreakREAL。输出参数滤波后工程值rEngValueREAL、未滤波工程值rEngRawREAL、断线报警bBreakLineBOOL、超量程报警bOverRangeBOOL。内部变量AD值浮点中间变量rADReal、滤波中间值rRawPre、首次扫描标志bFirst。这里说一个细节AD原始值为什么不放在内部而是做成输入参数因为FB是通用模板它不应该知道外部通道具体挂在哪个地址上。调用时把对应的模拟量输入字传进来一个FB就能服务所有输入通道。这既是复用原则也是编译能被多个实例同时调用的前提。3.2 核心ST代码与逐行解释在CX-P的功能块编辑器里语言可以选择ST结构化文本。我用自己的话复述一遍核心代码逻辑细节上你可以按实际量程微调。FUNCTION_BLOCK AI_CHANNEL VAR_INPUT wADValue : UINT; // 模拟量原始AD值 0~6000 rEngLow : REAL; // 工程量下限 rEngHigh : REAL; // 工程量上限 rFilter : REAL; // 滤波系数 0.0~1.0 rBreak : REAL; // 断线判定阈值AD值 END_VAR VAR_OUTPUT rEngValue : REAL; // 滤波后工程值 rEngRaw : REAL; // 未滤波工程值 bBreakLine : BOOL; // 断线报警 bOverRange : BOOL; // 超量程报警 END_VAR VAR rADReal : REAL; rRawPre : REAL; bFirst : BOOL : FALSE; END_VAR // 首次扫描把当前AD值作为滤波初值避免启动瞬间输出跳变 IF NOT bFirst THEN rRawPre : UINT_TO_REAL(wADValue); bFirst : TRUE; END_IF; rADReal : UINT_TO_REAL(wADValue); // 断线粗判4~20mA模式下断线AD值通常会跌到接近0 IF rADReal rBreak THEN bBreakLine : TRUE; ELSE bBreakLine : FALSE; END_IF; // 一阶惯性滤波输出 上次输出 系数 * (当前输入 - 上次输出) rRawPre : rRawPre rFilter * (rADReal - rRawPre); // 工程量线性换算AD / 6000 * 量程 下限 rEngValue : rRawPre / 6000.0 * (rEngHigh - rEngLow) rEngLow; rEngRaw : rADReal / 6000.0 * (rEngHigh - rEngLow) rEngLow; // 超量程判断超出上下限的10%范围内视为有效 IF (rEngValue rEngLow - (rEngHigh - rEngLow) * 0.1) OR (rEngValue rEngHigh (rEngHigh - rEngLow) * 0.1) THEN bOverRange : TRUE; ELSE bOverRange : FALSE; END_IF;这段代码里最值得琢磨的是“首次扫描初始化”。如果不做这一步FB上电瞬间滤波中间值rRawPre是0而实际AD值可能是3000那么滤波输出会从0慢慢爬向3000现场表现就是液位显示从0突然蹿到实际高度虽然只持续几百毫秒但如果你用这个值去控制阀门或者触发联锁启动瞬间的误动作可能带来严重后果。加一个首次扫描标志把初始值直接赋成当前AD值启动就是稳态。3.3 滤波、断线和超量程报警的三重保护逻辑滤波我用的是最经典的一阶惯性滤波当前输出等于上次输出加上系数乘以本次差异。系数rFilter越大跟踪越快但越容易受干扰系数越小曲线越平滑但滞后越明显。现场经验是0.1到0.3之间最常见具体数值需要根据信号的噪声水平和控制要求来调这个留在第5节详细说。断线判断和超量程判断其实是两道不同的关卡。断线判断是针对“AD值异常跌落”做的快速响应量程判断是针对“工程量超出正常范围”做的稳态响应。两道关卡用不同的输出标志就是为了让HMI能区分“传感器故障”和“工艺异常”。生产现场这两种状态的处置方式完全不同前者要派人查线后者要调操作。4. 在CX-P里创建和调用这套FB4.1 新建功能块的具体步骤在CX-P工程树里找到“功能块”右键选择“新建功能块”输入名称AI_CHANNEL语言选择ST然后双击打开功能块编辑器。编辑器左侧是变量表右侧是文本代码区把上一节的变量声明和ST代码粘进去编译通过后这个FB就生成了。这里有个容易忽略的细节功能块的编译和整个工程的编译是分开的。FB编辑完成先单独编译一次出现语法错误会直接提示比如变量类型不匹配、IF没有END_IF。只有FB本身编译通过梯形图里才能调用它。我习惯在每次修改FB后立刻编译绝不攒到最后因为FB被多处实例化以后一个语法错误会带来一大串调用处的报错排查起来很麻烦。4.2 梯形图实例化与多通道复用在梯形图编辑器中找到“调用功能块”指令选择AI_CHANNEL输入实例名称。实例名称就是这一路通道的“身份标识”比如AI_L1表示液位1AI_T3表示温度3。确定实例名后把梯形图里对应的模拟量输入字填到wADValue入口。比如本体内置模拟量第一路输入地址是2000调用时就在wADValue参数位置填“2000”量程上下限填实际物理范围。程序扫描到这条调用指令时FB会执行一次完整处理。四路输入就建四个实例十六路就建十六个实例代码本体不需要任何修改这就是“模板复用”的核心价值。不过要注意CP1H的JR指令区域和FB调用指令的实例数量有限制具体限制值可以参考硬件手册。实际应用中几十个模拟量实例完全够用但如果在一个程序里同时实例化几十上百个复杂FB资源占用可能会突破上限这时候就要考虑拆分程序或者精简FB内部逻辑。4.3 和HMI、PID指令怎么配合FB输出的是REAL浮点数而HMI和很多老式的PID指令习惯访问DM区或CIO区的字。因此需要把FB输出“搬运”到可访问的地址。常见的做法是在梯形图里用传送指令把AI_1.rEngValue传给D100HMI地址就映射到D100。注意REAL是32位浮点传送要成对处理或者直接用浮点传送指令避免只搬了一半导致HMI显示乱码。如果用CP1H内置的PID指令做闭环控制可以把FB输出的rEngValue作为PID指令的输入测量值PV再把PID输出结果送给模拟量输出通道。这里有一个需要注意的点滤波后的信号送往PID做PV、还是用未滤波的rEngRaw做PV要结合控制对象的特性决定。液位、温度这类大惯性对象用滤波值很舒服压力控制对象追求快速响应滤波系数太大会导致相位滞后反而引发振荡。我一般把rEngRaw也保留输出就是为了在调试时对比两条曲线选择更合适的信号源。5. 现场调试大揭秘最容易翻车的五个细节5.1 数值乱跳滤波系数该怎么定模拟量数值跳变是现场最常见的故障表现但跳变的原因五花八门。先排除硬件问题屏蔽、接地、传感器供电剩下就是滤波参数没匹配好。滤波系数rFilter的经验取值可以这样判断把滤波后的曲线和原始曲线放到同一画面观察如果原始值抖动幅度大但趋势明显滤波曲线应当只保留趋势、去掉毛刺如果滤波曲线还在明显抖动说明系数偏大需要往0.1以下调如果滤波曲线平滑到对阶跃变化反应迟钝说明系数偏小需要往0.3以上调。我曾经在一个现场为了消除液位信号的毛刺把滤波系数一路降到0.05结果液位实际已经上涨了20厘米显示值还慢吞吞往上爬。操作工当场就急了。后来把系数回调到0.15同时处理了屏蔽层接地问题两边都解决了。滤波是补救不是根治能接线解决的干扰别靠参数硬扛。5.2 断线误报4-20mA回路和供电的坑FB里断线判断用的是AD值跌落到接近0的粗判逻辑这在大多数场景下够用但有一个典型误报场景传感器供电电压不足。某个变送器需要24V供电如果现场离PLC柜太远、线径又细满量程时压降过大变送器输出电压会低于最低工作电压导致信号异常跌落AD值瞬间接近0FB判定断线。实际检查后发现传感器没断是供电塌了。处理办法是把传感器供电单独走线或者用隔离配电模块给每个变送器独立供电同时把断线阈值rBreak设置得比正常信号下限明显更低。4~20mA对应AD下限应为0正常工作时即使零液位也至少有约0.2mA的零点电流AD值不至于掉到0。如果阈值设得太靠近正常下限一点小波动就会触发误报警设得太低真断线时又反应迟钝。我用下来觉得设在满量程的2%~5%是比较均衡的位置。5.3 转换精度与零点校准别只信出厂值欧姆龙模拟量模块的出厂精度通常可以满足一般工程要求但现场经过一段时间运行后零点漂移和增益偏差是客观存在的。尤其是环境温度变化大的现场冷态和热态下同一路信号的AD值会差不少。有些项目要求精度很高就需要做现场两点校准。做法不复杂用信号发生器给模块一个标准的4mA信号记录实际AD值作为零点再给一个标准的20mA信号记录AD值作为满量程然后据此调整FB内部的换算公式。如果你的FB里能把“零点校准值”和“满量程校准值”做成输入参数那最好如果不想改接口也可以在PLC设定中通过模拟量配置的偏置校准功能来做。注意做完校准要复核中间点比如12mA对应50%量程偏差在允许范围内才算合格。5.4 屏蔽、接地和电源纹波这一类问题和FB本身无关但对FB以外的影响极大。模拟量信号线必须用屏蔽双绞线屏蔽层只能单端接地通常是在PLC柜侧接地禁止两端都接。两端都接地会形成地环路地电位差会在屏蔽层上产生电流反而把干扰引入信号线这是很多隐蔽干扰的根源。传感器和PLC共用同一个开关电源时如果电源纹波偏大模拟量信号会同步抖动。我做过一个实验同一路4~20mA信号用普通的24V开关电源供电时AD值波动±15个字换了低纹波电源之后波动降到±3个字以内。遇到数值“温和跳动”且滤波效果不佳时先检查供电电源比盲目调滤波系数有效得多。6. 输出通道也能复用同一套设计思路6.1 模拟量输出FB限幅与工程量反向换算模拟量输出和输入正好是反向过程工程量换算成DA值然后在模块内部变成标准电压或电流信号。反向换算的思路完全一致DA值 (工程值 - 工程量下限) / (工程量上限 - 工程量下限) * 6000唯一的区别是输出必须做限幅。驱动阀门开度、变频器频率这类执行机构时如果程序里某个计算环节出现异常输出值瞬间变成满量程可能导致阀门全开或者变频器频率飞车。我的习惯是在输出FB里做双保险第一层限制DA值只能在0~6000范围内第二层在工程量层面做工艺限幅比如阀门开度最多给到90%执行机构最大频率不超过48Hz。这道保护放在FB内部等于加了最后一道安全锁。6.2 手动/自动无扰动切换输出通道配套的另一个难点是手动/自动切换。手动模式下操作员从HMI输入设定值自动模式下PID或其他算法计算输出值切换瞬间如果两个值不一致输出会跳变执行机构猛动一下轻则影响工艺重则损坏设备。无扰动切换的标准做法是切换发生前的那个扫描周期把当前实际输出值同步写入另一个模式的目标值里。比如从手动切自动时先把PID的输出初值设成当前手动值让PID从当前位置开始接管从自动切手动时先把HMI手动设定值同步成当前自动输出值。说得直白一点就是让两个来源的数值在切换之前先“握手对齐”。这个逻辑可以放在FB内部也可以放在外层程序但我建议放在FB内部和输出FB一起形成一个完整独立的功能单元。如果你准备把输入输出两套FB综合起来做闭环控制结构就会非常清晰输入FB管采集和状态PID指令管道运算输出FB管执行和安全。三层各司其职程序可读性比散落的梯形图高一个档次。最后再唠叨一句我的个人体会FB绝对不是写一次就完事的现场调试过程中你一定会反复修改滤波系数、报警阈值和校准参数。每改一次就在FB内部注释里记一次修改日期和原因三个月的项目下来翻看注释你会感谢这几个字的记录。另外别把所有的保护全塞在一个FB里断线报警和超量程报警分口输出HMI才能做分级报警。我自己就曾经因为改滤波系数忘了同步调整断线阈值现场断电时全线误报排查半天才发现是阈值设置的问题。从那以后我调整任何一个参数都会把同一FB里其他相关参数的边界条件重新捋一遍这个习惯帮我省掉了无数个半夜电话。