简介:本资源是面向西门子TIA博途V15平台开发者的实用型全局功能块(FC)库文件,专用于高效计算整数(INT)或字(WORD)数据二进制表示中“1”的个数,适用于位逻辑判断、状态压缩分析、通信校验及PLC底层数据处理等工业自动化典型场景。压缩包共9个文件,含6个XML(定义FC接口与逻辑结构)、1个PLF(项目库框架文件)、1个IDX(索引支持快速加载)及1个AL15(TIA博途V15专用编译目标文件),整体体积仅290KB,轻量易集成。目前已有313人学习下载,说明其在实际工程中具备较高复用价值。用户可直接导入该FC库,在任意S7-1200/1500项目中调用,无需重复编写位移+掩码+累加的底层算法;配套结构清晰,支持参数化输入与整型输出,且经V15环境验证兼容,显著提升位运算类逻辑的开发效率与代码可靠性。
1. 项目概述:一个被低估的PLC编程基本功
在西门子TIA博途(TIA Portal)的编程世界里,尤其是处理过程数据、状态字或者通信协议时,我们经常会遇到一个看似简单却非常实用的需求:统计一个整数(INT, DINT)或者一个字(WORD, DWORD)数据中,二进制位为“1”的个数。这个功能,业内常称为“Population Count”或“位1计数”。比如,你可能需要统计一个16位输入模块的状态字中有多少个通道处于激活状态,或者分析一个32位的故障代码寄存器里同时触发了多少个故障位。虽然TIA博途提供了丰富的系统函数,但偏偏没有直接提供一个现成的“位1计数”指令。因此,自己动手封装一个高效、可靠的全局函数(FC),并将其打造成可复用的库文件,就成了每个中高级PLC程序员工具箱里的必备品。
今天要拆解的这个项目“TIA博途-计算整数或WORD里面1的个数-全局FC库文件-V15版本.zip”,正是这样一个典型的工程实用工具包。它不仅仅是一个简单的FC块,更代表了一种规范的、可移植的解决方案设计思路。我将结合自己多年在自动化项目中的实战经验,为你深度解析如何从零构建这样一个工具,并分享在V15环境下将其封装成库、应用到实际项目中的全流程细节与避坑指南。无论你是刚接触博途的新手,还是想优化自己工具箱的老手,这篇文章都能让你获得可直接“抄作业”的干货。
2. 核心需求与方案设计思路拆解
2.1 为什么需要这个功能?
在工业自动化场景中,二进制位操作是底层逻辑的基石。一个INT(16位)或DWORD(32位)变量,其每一个bit都可能代表一个具体的物理意义:一个传感器的状态、一个阀门的命令、一个报警标志、或者协议帧中的一个特定标志位。
典型应用场景举例:
- IO状态快速汇总:一个16通道的DI模块,其输入状态被映射为一个WORD(16位)。通过统计其中“1”的个数,可以瞬间知道有多少个传感器被触发,而无需逐个位判断。这在需要计算“有效信号数量”的场合,比如计算流水线上工件的数量,非常高效。
- 故障码分析:设备故障寄存器通常是一个DWORD,不同的bit代表不同的故障类型。统计“1”的个数可以快速评估故障的复杂程度(是单一故障还是多重并发故障),为上位机HMI显示或维护决策提供依据。
- 通信协议处理:在某些自定义或标准协议(如Modbus的线圈状态)中,数据包内可能包含多个状态位。统计“1”的数量可用于数据校验或快速解析。
- 算法与逻辑优化:在一些特定的控制算法或逻辑判断中,位1的数量本身就是一个关键参数。
TIA博途自带的指令如AND、OR、XOR以及移位指令,能完成位的操作,但没有直接的计数指令。因此,自己实现一个FC_CountBits是必然选择。
2.2 方案选型:循环移位法 vs. 查表法
实现位1计数,主要有两种经典算法,我们需要根据PLC的特点进行选择。
2.2.1 循环移位法(逐位检查)这是最直观的方法。将待统计的数据放入一个临时变量,然后循环其位数次(INT循环16次,DINT循环32次)。每次循环中,检查最低位(LSB)是否为1,如果是则计数器加1,然后将数据逻辑右移一位。
- 优点:逻辑清晰,代码简单,易于理解和调试。内存占用极小。
- 缺点:执行时间与数据位数成正比。对于32位数据,固定需要32个循环周期,在追求极致速度的超高速循环任务(如运动控制中断)中可能不是最优。
- PLC适应性:在绝大多数PLC应用场景中,OB1的循环周期在几十到几百毫秒,一个32次的循环消耗的时间可以忽略不计。因此,此方法在PLC中通常是首选,因其在可读性和性能间取得了最佳平衡。
2.2.2 查表法预先定义一个数组(如0-255共256个元素的字节数组),存储每个字节(8位)所有可能值(0-255)对应的位1个数。统计时,将待统计的32位数据拆分成4个字节,分别查表并将结果相加。
- 优点:速度极快,只需几次内存访问和加法运算,时间复杂度为O(1)。
- 缺点:需要额外的存储空间(256个字节)。在小型PLC(如S7-1200基本型)中,需要谨慎评估工作内存的占用。代码稍复杂。
- PLC适应性:适用于对执行速度有极端要求的场合,或者在大型数据块上频繁进行此操作的情况。对于单个变量的偶尔统计,优势不明显。
我们的选择:考虑到本项目的通用性、可读性以及PLC的典型应用环境,我们将采用循环移位法作为FC的核心算法。同时,我们会设计FC,使其能同时兼容INT/WORD(16位)和DINT/DWORD(32位)的输入,并通过一个输入参数来区分,以增强其通用性。
2.3 全局FC库的设计考量
为什么强调“全局FC库文件”?这体现了工程化的思维。
- 可重用性:一次编写,在所有项目中通过库直接调用,无需重复创建。
- 一致性:确保所有项目都使用同一套经过验证的、无错误的逻辑。
- 维护性:当发现优化点或BUG时,只需更新库文件,然后刷新项目中的库实例即可,无需修改每个项目中的代码。
- 知识沉淀:将个人或团队的优秀代码片段沉淀为组织资产。
在TIA博途V15中,这意味着我们需要创建一个“全局库”,并将编写好的FC放在其中,最后将整个库导出为.library文件或压缩包,方便分发和导入。
3. FC函数块详细设计与编程实现
3.1 接口定义(Input/Output/InOut)
一个设计良好的FC,接口必须清晰、健壮。以下是FC_CountBits的推荐接口定义:
Input参数:
DataToCount(IN): 要统计的数据。类型应为Variant(变体)吗?不,在SCL中更常用的方式是使用Any类型或重载。但在LAD/FBD中,更简单的做法是创建两个不同版本的FC,或者使用Union结构。这里我们采用一个更实用的方法:定义一个DINT类型的输入,并用另一个输入指定其有效长度。DataType(IN): 数据类型选择。Word类型,定义常量:0-代表16位(INT/WORD),1-代表32位(DINT/DWORD)。Execute(IN): Bool型,上升沿触发执行。这是标准单功能FC的良好实践,避免每个扫描周期都执行。
Output参数:
CountResult(OUT):INT类型。输出统计出的“1”的个数。对于16位数据,结果范围0-16;对于32位,范围0-32。Done(OUT): Bool型,执行完成标志位,Execute上升沿后一个周期内置位。Error(OUT): Bool型,错误标志位。ErrorID(OUT):Word类型,错误代码。例如:0-无错误,1-数据类型参数非法。
InOut参数:
TempData(IN_OUT):DINT类型,用作内部移位的临时变量。声明为IN_OUT是为了避免使用静态Temp变量,使FC成为“无存储”的纯函数,更适合背景数据块调用。
注意:在TIA中,对于简单的FC,也可以不使用
IN_OUT而直接使用局部变量Temp。但使用IN_OUT将中间变量“暴露”出来,虽然调用时稍显麻烦(需要传入一个实参),但能使FC的内部状态完全由调用者管理,提高了可重入性和透明度,在多任务调用时更安全。这是一个编程风格的选择,本项目为追求通用性,采用IN_OUT方式。
3.2 核心算法实现(以SCL语言为例)
TIA博途支持多种编程语言,结构化文本(SCL)因其类似高级语言的特点,在此类算法实现上比梯形图(LAD)更简洁。以下是FC_CountBits的SCL核心代码:
FUNCTION FC_CountBits : Void VAR_INPUT Execute : Bool; // 触发信号 DataToCount : DInt; // 待统计数据,以DInt形式传入,根据DataType决定使用低16位还是全部32位 DataType : Word; // 0=16位, 1=32位 END_VAR VAR_OUTPUT CountResult : Int; Done : Bool; Error : Bool; ErrorID : Word; END_VAR VAR_IN_OUT TempData : DInt; // 内部移位用临时变量 END_VAR VAR_TEMP LoopCount : Int; BitMask : DInt; END_VAR // 初始化输出 Done := false; Error := false; ErrorID := 0; CountResult := 0; // 上升沿触发执行 IF Execute AND NOT #LastExecute THEN #LastExecute := TRUE; // 静态变量或边沿检测标志,此处简化表示 // 参数检查 IF NOT (DataType = 0 OR DataType = 1) THEN Error := TRUE; ErrorID := 1; // 数据类型错误 RETURN; END_IF; // 初始化临时变量和循环计数器 TempData := DataToCount; IF DataType = 0 THEN // 16位模式:确保只处理低16位,高16位清零(可选,但更安全) TempData := TempData AND 16#0000FFFF; LoopCount := 16; ELSE // 32位模式 LoopCount := 32; END_IF; // 核心循环:统计位1的个数 FOR #i := 1 TO LoopCount DO // 检查当前最低位(LSB)是否为1 IF (TempData AND 16#00000001) <> 0 THEN CountResult := CountResult + 1; END_IF; // 逻辑右移一位 TempData := SHR(IN := TempData, N := 1); END_FOR; // 设置完成标志 Done := TRUE; ELSIF NOT Execute THEN #LastExecute := FALSE; // 复位边沿检测标志 END_IF; END_FUNCTION代码关键点解析:
- 边沿检测:使用
Execute信号的上升沿触发,是确保单次执行、避免每个扫描周期重复计算的标准模式。实际项目中,通常使用P(上升沿)指令或静态变量模拟边沿检测。 - 参数安全:对
DataType输入进行了有效性检查,防止传入非法值(如2,3等)导致循环次数错误。 - 16位数据处理:当选择16位模式时,使用
AND 16#0000FFFF将高16位显式清零。这是一个重要的防御性编程技巧。因为传入的DataToCount是DINT,可能包含高16位的随机值,如果不清零,统计的将是整个32位中低16位的1的个数,逻辑上正确但概念上不纯粹。显式清零使意图更清晰。 - 循环与移位:
FOR循环执行固定的16或32次。SHR是逻辑右移指令,移出的位用0填充。检查最低位使用AND 1的操作。
3.3 使用梯形图(LAD)的替代实现
如果你的项目规范要求使用LAD,也可以实现,但会繁琐很多。核心逻辑需要使用循环指令LOOP配合跳转标签,以及位逻辑和移位指令。可读性远不如SCL。这也说明了在TIA博途中进行算法和复杂逻辑编程时,SCL的巨大优势。
4. 封装为全局库与版本管理
4.1 在TIA Portal V15中创建全局库
- 新建库:在项目视图的“项目树”中,右键点击“库”,选择“新建库”。给库起一个清晰的名字,例如
MyStandardLib_V15。 - 设置库属性:在库的属性中,可以设置作者、版本(如1.0.0)、注释。版本号非常重要,是后续升级管理的依据。
- 添加FC块:在新建的库中,像在普通项目中一样,添加一个新的FC,命名为
FB_CountBits(注意:在库中,块名称最好具有唯一性和描述性)。将上一节编写的SCL代码粘贴进去。 - 编译与测试:在库内创建一个“测试程序”,添加一个PLC设备(如S7-1500),在Main OB中调用这个FC,并模拟运行,验证其功能是否正确。务必进行边界测试:输入全0、全1、0x5555(0101...)、0xAAAA(1010...)等典型值。
4.2 库的导出与归档
库开发测试完成后,需要将其导出,才能在其他项目中使用。
- 导出为库文件:右键点击你的库
MyStandardLib_V15,选择“归档为...”。TIA会生成一个.library文件。这个文件包含了库的所有信息(块、类型、工艺对象等)。 - 创建交付包:仅仅
.library文件可能还不够。为了完整交付,特别是包含版本信息和说明,最好将以下文件打包成ZIP(正如项目标题所示):MyStandardLib_V15.library(核心库文件)Readme.txt(说明文档,包含库版本、包含的块及功能简介、使用示例、注意事项)Version_History.txt(版本更新日志)- 可选的测试项目文件
.ap15,供使用者参考。
文件命名规范:项目标题“TIA博途-计算整数或WORD里面1的个数-全局FC库文件-V15版本.zip”就是一个很好的范例,清晰包含了工具名、功能、类型、版本和压缩格式。
4.3 在其他项目中导入与使用
- 导入库:在新项目的“库”视图中,点击“工具”>“全局库”>“打开全局库”,然后选择你的
.library文件。 - 引用块:库被加载后,你可以在程序块的“库”文件夹下找到它。直接将
FC_CountBits拖拽到你的OB/FC/FB中调用即可。 - 更新库:如果库作者发布了新版本(如V1.0.1),你可以在库上右键选择“更新库”,TIA会尝试合并更改。注意:如果接口定义发生变化(如增加输入参数),更新可能导致原有调用报错,需要手动调整。因此,库的接口设计应力求稳定。
5. 高级应用、优化与故障排查
5.1 性能优化探讨
虽然循环移位法已满足99%的需求,但如果你确实需要极致性能,可以考虑以下优化:
- 使用查表法:如前所述,创建一个包含256个字节的全局数据块
DB_BitCountTable,在启动组织块OB100中初始化这个表(计算0-255每个数的位1个数并写入)。然后在FC中,将输入的DINT拆分成4个字节,通过PEEK指令或指针寻址分别查表并累加。这种方法将32次循环+32次位判断+32次移位,减少为4次查表+3次加法,速度提升一个数量级。 - 使用“位并行”算法:这是一些编译器优化的算法,通过巧妙的位运算,在常数次操作内完成统计。例如著名的“MIT HAKMEM”算法。但在PLC的SCL中实现此类算法代码晦涩,且可能因PLC指令集限制无法达到预期效果,不推荐常规使用。
实操心得:除非你在处理高速IO中断(OB9x)或极短的时间片循环,否则完全不需要优化。PLC程序的可读性和可维护性的价值,远高于这微秒级的性能提升。清晰的循环移位法永远是第一选择。
5.2 扩展功能:统计任意长度数据块中的位1
有时我们需要统计一个数据区(如Array[1..100] of Byte)中所有位为1的总数。我们可以基于现有的FC_CountBits进行扩展。
思路:创建一个新的FC_CountBitsInArray,输入参数为数组的起始指针和长度(字节数)。内部循环遍历每个字节,调用FC_CountBits(设置为8位模式,或直接内联一个字节的查表操作)进行统计并累加。
这体现了库的另一个好处:基础功能块可以作为更复杂功能块的构建基石。
5.3 常见问题与调试技巧实录
问题1:调用FC后,CountResult输出始终为0。
- 排查步骤:
- 检查
Execute信号:是否确实产生了上升沿?确保调用时Execute管脚连接的是一个脉冲信号(如M0.0的上升沿),而不是常“1”信号。可以在FC内部第一行添加一个临时变量tempDebug赋值,在线监控看程序是否真的扫描进去了。 - 检查输入数据:在线监控
DataToCount的输入值,确认其值非零。尝试输入一个已知位1个数的数,如16#000F(二进制1111,应有4个1)。 - 检查
DataType:确认DataType输入的是0还是1,是否与DataToCount的实际位数匹配。 - 单步调试:如果TIA版本支持,使用SCL编辑器的调试功能设置断点,单步执行,观察
TempData的移位过程和CountResult的累加过程。
- 检查
问题2:统计32位数据时,结果偶尔错误。
- 可能原因:
DataToCount是DINT(有符号整数)。当输入一个负数(最高位为1)时,如果你在16位模式没有进行高16位清零操作,并且使用了算术右移(SAR)而不是逻辑右移(SHR),可能会导致符号位扩展,造成统计错误。 - 解决方案:确保在FC中始终使用逻辑右移
SHR,并对16位模式下的输入数据执行AND 16#0000FFFF操作,如上文代码所示。
问题3:导入库后,调用FC时提示“块未编译”或“接口不匹配”。
- 排查步骤:
- 重新编译库:在源项目中,右键点击库,选择“编译”>“软件(全部重建)”。
- 检查TIA版本兼容性:V15的库不能直接在V14或V16中打开(可能可以,但存在兼容性问题)。确保使用环境与库的版本一致。标题明确指明“V15版本”。
- 检查块保护:如果源FC设置了“专有技术保护”(Know-How Protection),在没有密码的情况下,你将无法看到内部代码,但通常不影响调用。如果调用失败,可能是保护导致编译问题,需联系库作者。
问题4:在多次频繁调用FC时,似乎影响了PLC的扫描周期。
- 分析:如果在一个扫描周期内,成百上千次地调用该FC(例如在大型循环中),累积的循环次数(上千*32次)可能会增加扫描时间。
- 优化建议:
- 评估是否真的需要如此密集的调用。能否将计算结果存储起来复用?
- 考虑使用查表法FC替代,大幅减少单次调用的指令负担。
- 将非实时性要求的统计任务放到循环中断OB(如OB30)中执行,减轻主OB1的负担。
一个实用的调试技巧:创建测试用例DB建立一个专门的数据块DB_Test_BitCount,里面定义一系列测试用例和预期结果。
| 变量名 | 数据类型 | 测试值 (十六进制) | 预期结果 (位1数) | 实际结果 | 备注 |
|---|---|---|---|---|---|
| Test_Case_1 | DInt | 16#00000000 | 0 | 全零 | |
| Test_Case_2 | DInt | 16#FFFFFFFF | 32 | 全一 | |
| Test_Case_3 | DInt | 16#55555555 | 16 | 0101... | |
| Test_Case_4 | DInt | 16#0000AAAA | 8 | 16位测试 | |
| Test_Case_5 | DInt | 16#80000001 | 2 | 符号位和最低位 |
在OB100或首次调用时,自动运行这些测试用例并与预期结果比较,如果不一致则置位一个报警位。这能在项目初期就发现FC在特定平台或环境下的潜在问题,实现“自检”。
本文还有配套的精品资源,点击获取