
做运动控制、卷绕设备、升降台、旋转工作台这类项目的人应该都有过这种经历现场编码器转起来了PLC里脉冲值也有了但要把它变成“转了几圈、当前在第几圈、圈内位置是多少”反而要折腾半天。这篇文章就是围绕博途TIA Portal环境下用SCL实现编码器转动圈数和单圈脉冲数计算算法展开的从硬件通道选择、变量表设计、核心换算代码到现场调试中的各种坑位把完整实现路径讲一遍。适合正在调试编码器计数、想用SCL做位置换算或者被丢脉冲、反转显示异常折腾过的工程师参考。实际项目中编码器原始输出只是一串脉冲设备真正关心的是机械位置。比如收卷机要知道当前卷径、旋转台要显示回零后的角度、升降机要判断到达第几层。把这些需求拆到底最终都会落到两个数上累计转动圈数以及当前圈内已经走过的脉冲数。这个看起来像一次除法就能解决的算法放进SCL实现时会牵扯到高速计数通道读取、32位数据类型、正反转取余修正、掉电保持和Z相回零等多个环节。下面逐个拆开讲。1. 转动圈数与单圈脉冲数先搞懂编码器在“数什么”1.1 增量式编码器的A/B/Z信号与脉冲计数逻辑增量式编码器并不直接输出角度或圈数它输出的是一串方波脉冲。常见信号有三路A相、B相和Z相。A相和B相是相位差90度的两路方波它们的相位关系用来判断旋转方向A相超前B相认为是正转B相超前A相则是反转。Z相每转一圈出一个脉冲通常被用作圈零点或者参考基准。如果只用一路A相做单相计数就只能得到脉冲数量而无法判断方向所以绝大多数需要圈数和位置的场合都会选A/B双相输入。在PLC侧编码器使用可以抽象成三个量频率、累计值、方向。频率用于速度计算累计值就是高速计数通道对A/B信号边沿计数的结果方向则来自A/B相位差或者程序里对累计值增减的判断。SCL程序里真正拿来做圈数计算的是那个不断跳动的累计值。这里引入一个关键参数电子倍频。很多PLC的高速计数通道支持1倍频、2倍频、4倍频三种模式。4倍频的意思是同时捕捉A相和B相的上升沿与下降沿一圈下来计到的脉冲数量等于编码器物理刻线数的4倍。举个例子一个2500线的编码器4倍频后每圈就是10000个脉冲。为什么要专门说倍频因为程序里“单圈脉冲数”这个值的取值必须是经过倍频之后的实际计数脉冲数而不是编码器铭牌上的刻线数。这两者如果混用圈数结果会直接放大或缩小4倍后续位置换算全部偏移。第一次接触编码器的工程师往往在组态里选了4倍频程序里还按照2500这个刻线数去除结果转一圈HMI显示走了4圈现场一查一头雾水。把倍频逻辑先搞明白这个坑就能绕开。1.2 单圈脉冲数PPR与4倍频从铭牌参数到程序常量假设编码器铭牌标称2500P/R在4倍频模式下单圈脉冲数PPR就是2500乘以4等于10000。也就是说高速计数通道累计到10000个脉冲机械轴就转了一圈。这里的P/R全称是Pulses per Revolution即每转脉冲数是编码器最核心的电气参数采购时选多少线直接决定后续的角度分辨率。2500线4倍频后一圈10000个脉冲相当于每个脉冲对应0.036度对多数定位和卷绕场景已经够用。设备传动方式会让问题变复杂。比如编码器装在电机尾轴电机通过10比1减速机带动负载轴那么负载轴转一圈电机轴要转10圈编码器实际产生10倍的单圈脉冲量。这种场景下如果HMI要显示负载圈数程序里就不能直接用电机轴圈数而要额外除以减速比。我的建议是定义两级变量一级叫“电机脉冲圈数”由累计值除以PPR得到另一级叫“机械圈数”再除以传动比得到。两级分开现场排查时更容易定位HMI显示也直观。顺带提一下编码器刻线数常见有512、1024、2048、2500、3600、5000等有些不是整数次幂的数值。计算程序里尽量用整数除法把PPR定义为DINT类型常量或参数不要用浮点数到处近似。虽然圈数计算结果本身就是整数但如果中间过程把PPR写成了2500.0之类的浮点常量后续再做整数运算类型转换会引入莫名的问题。1.3 圈数算法的总设计思路一次除法背后的工程约束从数学上看圈数等于累计脉冲除以单圈脉冲数圈内位置等于累计脉冲对单圈脉冲数取余。一个除法加一个取余两行代码就能写完。但把这个公式放进PLC运行时还有四个工程约束要先想清楚。第一个约束是累计值的位数。高速计数通道计数值通常是32位有符号整数最大约21.4亿。若PPR是10000那么最多表达约21万圈对于行程很长的卷绕设备可能不够。遇到这种情况最简单的方法是把计数值分段保存用软件方式在高位扩展一个圈数计数器避免累计值本身溢出。第二个约束是取余的符号。SCL中的MOD运算在有负数参与时结果符号不一定符合数学定义。编码器反转时累计值是负数直接取余会得到负的余数必须修正到0到PPR减1区间。这个细节放到第3章详细展开。第三个约束是读取时刻。高速计数器是硬件计数与扫描周期无关但SCL读取计数值的瞬间两个扫描周期之间有一个同步过程。如果程序还要同时做速度计算把读取和换算代码放进循环中断组织块比放在主程序OB里更稳定数值变化也更平滑。第四个约束是断电重启的初始值。设备掉电后编码器位置丢失上电后是接着原来的圈数继续算还是根据机械原点重新回零再计数必须在程序里明确。增量式编码器没有绝对位置记忆多数项目采用上电回零方式一边使能轴一边找原点开关或Z相。这四点想清楚再写SCL后面基本不会大改。2. SCL编程前的硬件通道与变量设计2.1 为什么必须用高速计数器普通DI点到底差在哪编码器输出的本质是高频脉冲信号很多项目新手会问这不是开关量吗接到普通数字量输入点不就行了答案是不行。普通DI模块的信号要经过滤波电路、背板总线和用户程序扫描能可靠响应的频率很低。举个例子2500线编码器在3000转每分钟时4倍频后的脉冲频率约为200kHz普通DI通常连1kHz都难保证脉冲几乎全丢。这不是程序写得快不快的问题是硬件架构决定的。正经做法是把编码器A/B/Z信号接到支持高速计数的输入通道也就是HSC。博途里启用HSC有几个关键设置给通道分配硬件标识符HW ID在通道属性里选择计数模式还要选择合适的输入滤波时间。常见的HSC模式有单相计数、双相计数、带方向控制的计数和A/B四倍频计数。需要同时获得方向信息和圈数时A/B四倍频模式是首选。组态完成后HSC由硬件独立进行边沿计数不受扫描周期影响SCL程序要做的只是周期性读取累计值。还有一个现场接线要点。编码器信号常见有5V差分和24V集电极开路两种输出形式选型时需要注意与PLC输入规格匹配。接线必须用双绞屏蔽线屏蔽层在PLC侧单端接地走线尽量远离变频器输出电缆和动力线。HSC通道的抗干扰能力比普通IO强但也经不起长期挨着变频器主回路走线。接线正确是计数稳定的前提不要等现场丢脉冲了才回头补课。2.2 数据块与数据类型设计DINT、保持性一个都不能少程序里存放累计值、圈数、单圈位置的变量数据类型一律用DINT不要用INT。高速计数器输出的VALUE本身就是DINT如果为了省那一点存储空间转成INT一个2500线4倍频编码器转3圈就溢出变成负数或乱跳非常不值得。哪怕设备行程很短也用DINT起步这是成本最低、收益最大的保险。圈数和余数建议都用DINT保存。如果HMI要显示带小数的圈数可以另外建一个REAL变量做显示换算但内部核心计算仍然保持整数。这样做的好处是任何时候都能用圈数乘以PPR加上余数精确还原原始累计值便于校验。浮点类型虽然方便但大数运算时会丢精度做设备位置换算尤其要避免。掉电保持是容易被忽略的一环。如果圈数值放在普通数据块里设备一断电重启就清零。博途里可以把数据块设为保持性在DB属性里勾选“保持”或者把关键变量分配到带断电保持功能的存储区。保持性DB会占用断电保持存储空间需要提前估算够不够。不过要注意即使变量保持住了增量式编码器本身不记忆位置机械轴断电后如果被人挪动上电后的保存值和实际位置就对不上所以保持性设置主要用于恢复上次停机时的参考信息不能替代回零流程。另外强烈建议把PPR和传动比这类设备参数集中放到独立DB并开放给HMI修改。编码器型号更换、减速机调整时只改参数不动程序现场维护会轻松很多。这个习惯我是在一个交付项目里养成的最初PPR直接写在代码里当常量后来机械改版换减速机翻程序找常量、改完下载、重新验证非常被动。从那以后凡是设备固有参数一律做成可写变量不再写死。3. 核心算法实现从原始计数值到圈数与单圈位置的SCL代码3.1 通过CTRL_HSC读取当前累计计数值在S7-1200/1500平台下启用HSC通道后通过调用系统功能块CTRL_HSC来管理通道并读取当前计数值。这个功能块名字里带“控制”实际职责包含通道启停、方向设定、复位以及输出当前值。通常做法是在循环中断组织块中每个周期调用一次这样读取间隔固定后续做速度计算时也更方便。下面是一段典型的调用骨架。注意HWID是硬件标识符必须在设备组态中查到并填到程序里建议定义为常量不要散落在代码各处#hscCtrl( HWID : #HW_ID_HSC1, DIR : #bDirectionCtrl, EN_SIGNAL : #bEnableSignal, EnLoad : #bLoadParams, S_CU : #bCountUp, S_CD : #bCountDown, S_RES : #bReset, S_GATE : #bGate, S_FF : #bInterruptFlag, S_DIR : #bSetDirection, S_MODE : #bActivateMode ); #RawCount : #hscCtrl.VALUE;调用完成后功能块输出的VALUE就是当前的32位有符号累计值类型为DINT。这段代码是整个算法的输入来源。如果在组态里把HSC的当前值映射到了输入地址比如ID1000也可以用 #RawCount : %ID1000; 直接读取免去功能块调用。两种方式我都实测过CTRL_HSC输出信息更全适合需要状态监视和动态复位功能的项目直接读地址更简单适合纯测量场景。新手阶段建议用CTRL_HSC因为返回的状态值有助于排查问题。有一点需要提醒CTRL_HSC的HWID不能填错填错会直接报错或读取不到数据。HWID在设备组态里是自动生成的一个编号每个HSC通道都不一样。项目迁移到另一台PLC时HWID可能变化尽量做成一个可维护的变量表项别硬编码在OB里写死。3.2 核心换算代码DIV/MOD组合与负数取余修正到了整篇文章最核心的部分。假设累计值#RawCount可能是正数也可能是负数PPR为单圈总脉冲数我们要得到圈数#Turns整数和单圈内脉冲位置#PosInTurn0到PPR减1。一个通用且经过验证的SCL写法如下#Turns : #RawCount / #PulsesPerRev; #PosInTurn : #RawCount MOD #PulsesPerRev; IF #PosInTurn 0 THEN #PosInTurn : #PosInTurn #PulsesPerRev; #Turns : #Turns - 1; END_IF;为什么要加这个IF分支先说结论SCL里整数除法默认按向零截断处理MOD的结果可能与被除数同号。以-7除以3为例向零截断得到-2余数-1。但数学上我们希望-7对应的圈数是-3、余数是2因为-7等于-3乘以3加上2这样余数恒在0到2之间。修正逻辑就是在余数为负时给余数补上一个PPR同时圈数减1等式关系仍然保持圈数乘以PPR加余数等于原始累计值。这个修正完全是从物理含义出发的圈内脉冲位置概念上是无符号的0到PPR减1不论正转反转都不该出现负数。如果要把圈数和余数还原成原始累计值用于校验算法可以写#RestoreValue : #Turns * #PulsesPerRev #PosInTurn;我在写完第一版实现后每次都会把这个#RestoreValue放到在线监视列表里跟#RawCount比对。只要两者始终相等就说明取余修正没有破坏数据关系。不要小看这一步验证它能把取余符号这种隐蔽问题直接暴露出来。如果HMI要显示角度或长度需要把整数结果转成实际物理单位。比如旋转台一圈对应360度用REAL计算#AngleDegree : DINT_TO_REAL(#PosInTurn) * 360.0 / DINT_TO_REAL(#PulsesPerRev);注意先转换再乘除避免整数除法把小数吃掉。如果需要圈数的浮点显示值#TurnsReal : DINT_TO_REAL(#Turns) DINT_TO_REAL(#PosInTurn) / DINT_TO_REAL(#PulsesPerRev);这两条加上后HMI上既能显示整数圈又能显示带小数和角度的位置基本覆盖大多数画面需求。3.3 用Z相做每圈基准零点校准与绝对圈数重建前面的除法算法是基于连续累计脉冲的如果现场干扰比较大偶尔丢几个脉冲累计值就会带着误差一直走下去圈数也会跟着偏。想消除这种累计误差就得依靠Z相。Z相每转一圈输出一个脉冲相当于硬件给出的“圈界标”。在博途HSC组态中可以把Z相配置为复位信号每来一个Z脉冲硬件将当前计数值清零。这样一来计数值只表示当前这一圈内的位置即使中途丢了脉冲到下一圈零点也会被强制校准。但代价是CPU里的总圈数没法只靠累计值得到需要额外维护一个圈数计数器在Z相边沿发生时按方向加1或减1。更常见的做法是把Z相信号同时接入普通DI点在硬件中断或循环中断里捕捉Z相上升沿执行如下逻辑IF #bForward THEN #ZCount : #ZCount 1; ELSE #ZCount : #ZCount - 1; END_IF; #PosSinceZ : #RawCount;这里#ZCount表示已经经过的整圈数#PosSinceZ表示自最近一个Z相以来走过的脉冲数。这种“圈数加圈内位置”的表达比单纯累计值更符合人机工程也不怕丢脉冲。当然实际接线时Z相信号必须和A/B相时间上对齐否则零点会偏移。我建议在调试时用示波器同时观察A/B/Z三路信号确认Z相出现的位置相对A/B相是稳定的再写逻辑判断否则零点偏移问题单看程序很难发现。4. 现场调试中的典型坑与排查套路4.1 脉冲丢计数、数值乱跳先查干扰还是先查组态编码器现场最常见的故障是数值跳动、多计或少计。一看到这种情况很多人下意识改滤波、调程序其实应该先判断现象属于丢脉冲还是多计脉冲。如果数值偏少、圈数比实际小优先怀疑丢脉冲要查接线、屏蔽、接地以及HSC输入滤波时间是否过大。如果数值乱跳、时多时少优先怀疑干扰耦合进信号线注意屏蔽层末端单端接地走线远离变频器动力电缆。还有一类很有规律的现象电机一启动计数就乱电机停了又正常这种基本是动力线干扰或者编码器供电电压在电机启停瞬间跌落。排查时我习惯按三步走。第一步用示波器或万用表测编码器供电确认运行状态下电压稳定第二步看HSC诊断状态是否有硬件报警第三步手动慢速旋转编码器对照实际转过的角度和PLC显示值验证倍频和滤波设置。这三步做完大多数计数问题都能定位。这里有个很不起眼但影响巨大的参数HSC输入滤波时间。S7-1200的HSC通道有滤波选项默认值可能把高频脉冲滤掉不少。对200kHz级别的信号要把滤波设置到最快档或者合适的RC时间否则转一圈可能少几百甚至几千个脉冲。这个问题在组态界面上不显眼造成的影响却非常大我见过交付现场因为这个参数查了半天。4.2 反转时圈数显示异常SCL取余符号的坑与处理这条基本就是3.2小节代码的现场版。我第一版程序只写了除法加MOD没有取余修正正转测试全部正常一反转HMI上的圈内脉冲位置变成-800、-300这类负数操作员看到肯定会问是不是设备坏了。排查确认是SCL取余的符号问题。解决方式就是加上IF修正。如果想验证自己的SCL实现是否正确可以在程序里临时给#RawCount赋一个测试值比如-7000PPR为10000期望输出是圈数-1、余数3000因为-7000等于-1乘以10000加3000。如果输出不是这个结果基本可以判断取余修正到底有没有生效。显示层面也有讲究。HMI画面如果只显示圈数和方向箭头一般不会有问题但如果画面显示“单圈位置”或百分比比如“当前第3圈、剩余60%”就必须确保余数修正到0到PPR减1区间。还有的HMI用圈内脉冲数除以PPR算百分比如果余数还是负数百分比就变成负数这时候不是画面代码的问题而是PLC侧数据源的问题。4.3 断电重启数值归零保持性设置的两种实现前面提过要保住断电前的圈数值可以在博途里把DB设为保持性。具体有两种实现思路一是数据块属性里勾选保持性这是最常用的做法二是把关键变量分配到断电保持存储区这适用于S7-1500等支持较大保持区的CPU。S7-1200部分CPU保持区有限如果DB太大设置保持性时会提示空间不足此时要精简变量只对关键数据设置保持。不过要再强调一次增量式编码器本身不记忆位置断电后如果机械轴被人为移动保持的数值就无法作为绝对位置使用。所以设备开机流程必须包含原点搜索先回到机械原点或接近开关位置再通过Z相精定位然后才开始重新计数。保持值只能作为停机前位置的参考不能替代回零动作。程序层面可以设计一个“回零完成”标志回零完成后才允许后续定位指令执行这样能避免操作员在位置不明确的情况下误动作。4.4 编码器参数不匹配导致换算偏差的排查思路最后一个典型坑是换了编码器或改了传动后圈数整体偏大或偏小。这类问题排查起来不复杂按顺序核对几个值就行。第一确认编码器实际的单圈脉冲数。不要只信铭牌要看组态里的倍频。比如铭牌刻线数2500、倍频4PPR就应该是10000。如果程序里还按2500算结果就大了4倍。第二确认传动链。编码器在电机轴和负载轴上得到的圈数含义完全不同。电机轴转10圈负载轴才转1圈要把程序里的圈数除以减速比才能对应负载的实际圈数。第三确认有没有中间电子处理环节。比如驱动器内部带有电子齿轮或分频器PLC收到的脉冲数和编码器原始输出可能不一致此时PPR要以PLC实际接收到的每圈脉冲数为准不能只看编码器铭牌。我个人的调试习惯是在HMI做一个“试转一圈”按钮手动让轴转一整圈然后看程序计算的圈数是否刚好增加1。这个测试比翻公式直观得多如果不对按上面三个方向逐项排查基本几十分钟内能定位。编码器算法本身并不复杂现场真正花时间的往往是这些琐碎但必须较真的参数和接线细节。最后再分享一个小习惯我的所有编码器SCL程序里#RawCount原始累计值无论如何都要保留在DB里并开放监控。圈数显示不对时可以通过它判断是累加问题还是换算问题圈数显示正常时它也是验证算法正确性的底稿。另外PPR和传动比一定做成HMI可写参数换编码器、改减速机现场只改参数不动程序能省大量维护时间。编码器圈数和单圈脉冲数计算的工程实现本质上就是一个除法加取余但把数据流、符号、保持性、抗干扰这些细节都照顾到才能在现场稳定跑下去。