跑马灯这个题目,在PLC圈子里基本就是“Hello World”一样的存在。我以前在培训新人的时候,每次都会拿这个例子开头,不是因为简单,而是它刚好能把PLC开发里最核心的那几个环节全部串起来:工程怎么建、任务周期怎么设、定时器怎么用、数组和循环怎么写、变量怎么监控,最后再到可视化仿真里看效果。这一套走下来,你对整套开发工具的底子也就有了。
这次我用的是汇川AM系列PLC来做。AM系列在汇川产品线里属于中型PLC,基于Codesys内核,编程软件用InoProShop。很多刚接触的朋友可能从H5U或者Easy系列入门,但AM系列的逻辑和生态更接近国际主流,尤其是支持EtherCAT总线、支持多任务,仿真调试能力也强很多。用这个平台做跑马灯,虽然看着“杀鸡用牛刀”,但把流程跑通之后,后面做运动控制、做总线通讯,都是复用同一套思路。
整篇文章我打算按一个完整的项目流程来走:从方案选型开始,到工程配置、程序编写、仿真调试,再到真机切换,最后把常见问题梳理一遍。你可以当教程看,也可以直接照着操作,过程中的变量名、周期参数我都会给出具体值,方便你抄作业。
1. 跑马灯方案设计与技术选型
1.1 为什么用AM系列而不是小型机
先把结论放在前面:跑马灯这个需求本身,随便一台几十块钱的小型PLC都能做,用AM系列确实有些“浪费”。但如果你现在手里刚好有一台AM系列,或者你想为以后做总线控制、运动控制打基础,那用AM系列来练这个项目就非常合适。
AM系列和H5U、Easy系列最大的区别在于软件生态。H5U和Easy用的更多是厂家的专用软件体系,上手快,但功能边界也明显;AM系列走的是Codesys路线,这意味着你以后接触倍福、博途这类平台时,很多概念是直接相通的,比如POU、任务配置、库管理器、可视化,这套东西的通用性非常强。另一个很现实的原因是仿真能力:AM系列在InoProShop里可以完整跑一个软PLC出来,不需要接真实硬件就能验证逻辑,对于新手学编程、老手测算法都特别有用。
如果你用的是AM401、AM402甚至AM600,跑这个项目都没有任何问题。我个人的建议是,如果你只是学习,选AM402-CPU1608TP/TN就够了,自带16点输入、8点晶体管输出,还带以太网口,价格相对适中,练习IO、通讯、总线都能兼顾。后续就算你想挂EtherCAT伺服,这个型号也能撑起来。
1.2 跑马灯程序的三种主流实现路线
很多第一次做跑马灯的朋友会犯一个思维误区:上来就写一大堆输出线圈,一个灯一个定时器,8个灯写8段逻辑。这种写法不是说不能跑,而是改起来非常痛苦。比如你想从8个灯改成16个灯,那就要再复制8段逻辑,而且稍微一个定时器时间写错,整个流水节奏就乱掉了。
真正好的写法是三个方向,你可以根据自己习惯选:
第一种是移位寄存器思路。核心是把8个输出看成一个布尔数组,每个扫描周期检查定时器是否到时间,到了就把整个数组左移一位,然后把第一位或最后一位置1。这个思路最贴合“跑马灯”这个名称,逻辑也最容易理解。缺点是在Codesys里对数组做整体移位,需要配合FOR循环或者指针,对新手来说稍微有点绕。
第二种是计数器加数组索引的思路,也是我个人最推荐的方案。用一个字节变量做索引,每到一个周期索引加1,索引从0到7循环,然后每次只把数组里对应索引的那个元素置True,其余全部置False。这个方案的代码量最少,逻辑也最清晰,后面想改成双边流水、变速流水都特别方便。
第三种是用整数循环移位指令,比如SHL、ROL这类。直接在字节或字上做整体移位,效率最高,但可读性稍微差一些,而且如果你要控制单个灯的逻辑状态,还得再拆位判断,反而不方便。
这三种方案我会在后面的代码部分重点讲第二种,因为它在调试时最直观,而且方便你理解数组、循环、变量索引这几个基本功。
1.3 需求拆解与IO规划
动手写程序之前,先明确一下这个项目的输入输出。标准版跑马灯我按一个启停按钮、一个模式选择开关、8路灯输出来规划:
- 启动按钮:接PLC的DI点,或者仿真时用可视化按钮代替。信号为True时跑马灯开始跑,False时全部停止并复位。
- 模式选择开关:接另一个DI点,False是单方向流水,True是双向往返流水。这个开关可有可无,但加上它,你就能体会用同一个程序块通过参数切换不同模式的控制思想。
- 8路输出:对应8个指示灯。仿真时用可视化界面的8个圆形控件绑定;真机调试时接到AM402本体自带的8个晶体管输出点。
如果你用的是AM402-CPU1608TP/TN,本体DO点物理地址一般从Q0.0到Q0.7,正好对应8个灯。仿真时不需要关心物理地址,直接用内部变量就行,这也是仿真调试最舒服的地方——变量随便建,不会触发现实世界的任何设备。
2. 工程创建与运行环境配置
2.1 新建工程与选型
打开InoProShop软件之后,第一步是新建工程。如果你第一次用这个软件,它会提示你选择PLC类型和编程语言。我这边选的型号是AM402-CPU1608TP/TN,编程语言我先选了结构化文本ST,后面会把另外两种语言的写法也给你做个对照。
新建完成后,你会看到工程树里面有几个关键节点:程序组织单元POU、任务配置、可视化管理器、库管理器。这套结构和Codesys几乎一模一样,你用AM系列练会了,以后切到任何Codesys系平台都能秒上手。
这里有个细节要注意:工程名和PLC类型确定之后,后期不太好直接在软件里改,所以一开始就要选对。如果你手里的实际设备是AM401,程序里用到的功能不会超过AM402的能力范围,代码可以直接通用,工程选型不同影响不大。
2.2 总线配置该不该做
很多教程一上来就让你配置EtherCAT主站、添加远程IO从站。跑马灯这个项目真没必要,原因有两点:一是我们只用PLC本体自带的IO点,不走总线通讯;二是仿真模式下本身就没有真实从站,你就算配了EtherCAT从站,仿真器也扫描不到,反而会报错。
所以我的建议是:在这个项目里先跳过总线配置,直接把程序写好、仿真跑通。等你后期真正接了EtherCAT伺服或者扩展IO模块,再回来在Device节点下添加主站、扫描从站,那个时候你也会更清楚总线配置的来龙去脉。
如果你确实想顺带练一下总线配置,可以在真机连接状态下操作:右键Device节点,添加EtherCAT主站,然后用网线连接PLC和从站模块,点击扫描,软件会自动识别挂在总线上的设备。这个过程在仿真时做不了,只能真机操作。
2.3 任务周期与程序组织
AM系列的程序执行逻辑是“任务驱动”的,也就是说,你写的程序必须挂到一个任务底下,PLC才会按周期循环执行。默认情况下工程里会有一个Task(周期任务),里面挂载了主程序PLC_PRG。
点开任务配置节点,你会看到任务类型、优先级、周期(Interval)这些参数。跑马灯这类逻辑控制程序,我习惯把任务周期设在5ms到20ms之间。这里我用10ms,原因是:跑马灯的亮灯切换间隔是500ms,多任务周期下用定时器完全可以精准控制;10ms的周期对PLC算力消耗也低。如果你把任务周期设成1ms,CPU占用会明显提高,对跑马灯这种慢速逻辑没有任何必要。
这里还想强调一个点:把逻辑程序挂到任务里,很多人会忽略这一步,结果写了半天程序,下载到PLC里什么反应都没有。记住,POU如果不挂到任务下,它就像一本放在书架上的书,永远不会自己翻开去执行。
3. 跑马灯程序的核心实现
3.1 变量规划
我建议把跑马灯程序写在主程序PLC_PRG里,所有变量先规划好,不要边写边补。以下是完整变量清单:
PROGRAM PLC_PRG VAR xStart : BOOL := FALSE; // 启动按钮(仿真/真机绑定) xMode : BOOL := FALSE; // 模式切换:FALSE=单方向,TRUE=双向 fbTimer : TON; // 500ms 定时器 byIndex : BYTE := 0; // 当前点亮的灯索引(0~7) iDir : INT := 1; // 移动方向:1=右移,-1=左移 xRunLamp : ARRAY[0..7] OF BOOL;// 8路输出状态 END_VAR几个变量的设计理由我多说两句。byIndex用BYTE而不是INT,是因为8个灯用0到7的索引,BYTE完全够用,而且在Codesys里BYTE做比较运算时更省内存,类型也更安全。iDir用INT来记录方向,是为了实现双向流水功能时方便取反。xRunLamp作为输出数组,后期绑定到可视化控件时,每个元素对应一个灯,非常直观。
有些人可能习惯把输出直接写到Q地址,比如Q0.0到Q0.7。我不太推荐在程序内部直接驱动物理输出点,因为这样仿真和真机切换时会很麻烦,仿真时没有物理IO,输出地址往往直接失效。用内部变量做一层中转,仿真绑定可视化控件,真机再做一个IO映射,这种分层设计在真正的工业项目里是标准做法。
3.2 ST语言实现(推荐方案)
下面是核心程序,用的是计数器加数组索引的方案。
// 1. 定时器逻辑:启动按钮为True时,定时器开始计时 fbTimer(IN := xStart, PT := T#500MS); // 2. 定时时间到,进行一次切换动作 IF fbTimer.Q THEN // 先复位定时器,否则Q信号会一直保持True fbTimer(IN := FALSE); // 3. 更新索引 IF xMode THEN // 双向模式:到达两端边界时反向 IF byIndex >= 7 THEN iDir := -1; ELSIF byIndex <= 0 THEN iDir := 1; END_IF byIndex := byIndex + iDir; ELSE // 单方向模式:循环0→1→2...→7→0 IF byIndex >= 7 THEN byIndex := 0; ELSE byIndex := byIndex + 1; END_IF END_IF // 4. 根据索引刷新8路输出 FOR i := 0 TO 7 DO IF i = byIndex THEN xRunLamp[i] := TRUE; ELSE xRunLamp[i] := FALSE; END_IF END_FOR END_IF // 5. 停车处理:按钮松开,全部熄灭并复位 IF NOT xStart THEN byIndex := 0; iDir := 1; FOR i := 0 TO 7 DO xRunLamp[i] := FALSE; END_FOR END_IF这段程序里有三个细节你需要注意。
第一,定时器复位的方式。我在定时器的Q信号为True之后,马上把fbTimer的IN输入置为FALSE。这个做法在Codesys里是可行的,因为定时器功能块在输出Q为True之后,如果IN为FALSE,累计时间会被清零,下一次IN为True再重新计时。如果你不这么做,Q会一直保持True,索引会疯狂递增而不是等待下一个500ms。
第二,FOR循环里用了一个局部变量i。i需要在变量声明区里定义为INT类型,否则编译会报错。这个小细节看着不起眼,但经常有人漏掉。
第三,停车时的复位逻辑。很多人写停车只是简单把所有灯熄灭,但我特意把byIndex和iDir都复位了。这样做的原因是保证下次启动时,从第0个灯开始往右跑,而不是从某个中间状态继续跑。工业设备里这叫“上电初始状态”,虽然跑马灯无所谓,但这种习惯一定要养好。
3.3 梯形图与FBD的等价实现
我知道不少电工出身的朋友更习惯看梯形图。跑马灯这个项目用ST写确实舒服,但如果你公司的电气主管要求统一用梯形图,你也得会。
梯形图的核心思路是一样:先用TON实现500ms脉冲,然后用比较指令判断当前索引值,等于0就往左移动,等于7就重新赋0,中间状态就把索引加1。在InoProShop里,梯形图对数组的遍历比较麻烦,你没办法直接写FOR循环,只能把每个输出线圈单独写出来,用等于比较指令逐位判断。8个灯要写8个分支,逻辑很清晰,但代码量会翻好几倍。
FBD则介于两者之间,你可以在FBD里用TON功能块、SEL选择器、EQ比较器等组合起来。对老手来说,FBD画出来比梯形图更紧凑,但新手理解起来不如ST直观。
我的建议是:这个项目至少用一种文本语言跑通,再用一种梯形图语言对照看看。两种都理解了,你才算真正理解“PLC程序是一回事,只是表达语言不同”这件事。
3.4 扩展玩法:变速、渐变和双灯追逐
跑马灯学完之后,不要急着就丢到一边。我每次带人学这个案例,都会让他们继续扩展几个小功能,这个过程比跑马灯本身重要得多。
第一是变速效果。把定时器的PT参数从固定值改成一个变量,比如nSpeed是一个INT类型,取值范围500到2000,单位ms。你可以在可视化界面放一个数值输入控件绑定这个变量,仿真运行时一边调节速度一边观察效果。改动的代码只有一行:把T#500MS改成T#(nSpeed)MS,但你会立刻理解“参数化驱动逻辑”的意义。
第二是双灯追逐。我刚才的代码是一次点亮一个灯,你可以改成一次点亮连续两个灯,方法也很简单:在FOR循环里加判断条件,凡是索引等于byIndex或者byIndex+1的都置True,超过7就回绕。这样看起来就像两盏灯在赛跑。
第三是渐变效果。如果你以后要接的是调光模块(比如支持PWM输出的灯光控制器),可以让输出值不是一个布尔量,而是一个0到1000的整数,每次切换不是置0/置1,而是逐渐递减。这个功能已经能练到高级的数据变化和限幅处理了。
千万别小看这些扩展,它们就是你后面做复杂项目时用到的状态机、脉冲控制、速度控制的基础雏形。
4. 仿真调试全流程实录
4.1 编译与登录仿真器
程序写完之后,先点击编译按钮。编译通过后,在InoProShop的顶部菜单里找“在线”或者“登录”按钮。点击旁边的小箭头,可以看到下拉选项,里面有“仿真”和“USB/以太网”之类的选项。这里关键一步是选择仿真模式,软件会启动一个虚拟的PLC内核作为运行环境。
如果你点登录之后没反应,先看看左下角的输出窗口有没有报错。常见的是“没有连接设备”,这说明你没选仿真模式,软件还在尝试找你真实的PLC。还有一种是“仿真器启动失败”,这个多半是虚拟网卡或者权限问题,用管理员身份重新打开软件一般就能解决。
登录成功之后,软件会提示你要不要将当前程序下载到设备。这里直接点下载就行。下载完成后再点运行(F5),这时候你的程序就开始在虚拟PLC里跑了。
4.2 可视化界面的搭建与绑定
仿真跑起来之后,如果不做可视化,你只能通过监控表看变量值,不够直观。AM系列的可视化编辑器可以让我们直接拖控件出来构建一个虚拟操作面板,这也是我非常推荐大家练习的功能。
在工程树里找到可视化节点,右键添加一个新的可视化对象,然后点进去。左侧工具箱里你可以找到基本图形(矩形、椭圆)、指示灯、按钮、数值显示等控件。我们拖8个圆形出来,排列成一行,再拖一个启动按钮和一个停止按钮。
关键步骤是绑定变量。选中一个圆形控件,在属性窗格中找到“颜色”或“动态文本”相关的属性,点击绑定按钮,选择变量xRunLamp[0]。以此类推,8个圆分别绑定xRunLamp[0]到xRunLamp[7]。启动按钮绑xStart。绑定完成后,把当前可视化界面设置为启动画面,这样仿真登录后它会自动弹出。
到这里,你的仿真面板就做好了。点击运行后,再点一下可视化里的启动按钮,你会看到8个灯按顺序亮过去,500ms跳一次。如果你再切一下模式开关变量,还能看到双向来回流动的效果。整个交互非常直观,而且不需要任何物理设备。
4.3 监控表与强制变量的使用
光看可视化灯亮还不够,你还需要学会用监控表看底层变量,尤其是看定时器状态和索引值。
在工程树里的监控表节点,添加一个监控表,然后手动添加变量fbTimer.Q、byIndex、xMode、iDir,调试时把它们排在一起。代码跑起来之后,监控表会实时刷新这些变量的值。你会发现byIndex从0到7循环变化,fbTimer.Q每隔500ms跳一次True,整个过程一目了然。
如果你想在仿真时把某个变量强制为特定值,右键变量选择“强制”或者“写入”,可以直接给变量赋值。比如你想快速测试从第5个灯启动的场景,就直接把byIndex写入5,下一次切换会从5开始往右走。这个操作用来做边界测试非常方便,真机上就要小心了,因为强制信号一旦忘记取消,可能造成设备误动作。仿真阶段你尽管随便试。
4.4 从仿真切换到真机
仿真全部通过之后,如果你手头有真机AM402,可以准备把程序下载到实际设备里。切换流程很简单:停止程序运行,点在线菜单里的“注销”退出仿真;然后用网线连接电脑和PLC的以太网口,设置电脑本地IP地址和PLC同一网段(AM402默认IP一般可以在手册里查到,常见的是192.168.1.x段);重新点登录,选择以太网方式,下载程序并运行。
这时候还有一步映射要做:把程序里的xRunLamp数组映射到物理输出点。最简单的方式有两种,一种是在程序后面加IO映射代码:
Q0.0 := xRunLamp[0]; Q0.1 := xRunLamp[1]; Q0.2 := xRunLamp[2]; Q0.3 := xRunLamp[3]; Q0.4 := xRunLamp[4]; Q0.5 := xRunLamp[5]; Q0.6 := xRunLamp[6]; Q0.7 := xRunLamp[7];另一种是在IO映射表里做关联。我个人的习惯是用代码做映射,因为更直观,后期查问题一眼就能看到对应关系。把8个输出接上指示灯,启动按钮接DI点,按下按钮就能看到真机上一排灯跑起来,那感觉和仿真完全不一样。
5. 常见问题与排查技巧实录
5.1 编译报错速查
第一次写这个程序,编译阶段最常见的几个错,我整理成一个表:
| 报错现象 | 根本原因 | 解决方式 |
|---|---|---|
| 变量i未声明 | FOR循环条件里用了未经声明的变量 | 在VAR区增加i : INT; |
| fbTimer未声明 | TON功能块实例未创建 | 增加fbTimer : TON; |
| 类型不匹配byIndex>=7报错 | BYTE和INT混用 | 统一用BYTE,或者都转INT |
| PT参数期望时间为标准时间格式 | 定时器时间写法不对 | 用T#500MS格式,而不是直接写500 |
| POU未分配给任务 | 程序没有被任何任务调用 | 在任务配置里添加调用 |
这些都是很典型的低级错误,但新手几乎都会遇到。我的建议是编译报错不要慌,双击错误信息,软件会定位到具体行,很多错误看上下文就能猜出来。
5.2 运行时逻辑不对的排查思路
编译通过了不代表逻辑对。我见过很多人卡在几个奇怪现象上。
现象一:跑马灯没反应。先查xStart是否为True,再查定时器Q是否在规律跳动。如果Q一直在False,说明定时器IN没接收到启动信号;如果Q一直为True,说明定时器没被复位。这两点检查完,90%的问题都解决了。
现象二:灯亮一下就全灭。这个多半是定时器复位时机不对,或者是把复位写到了IF外面,导致每个扫描周期都重新清零。检查一下复位逻辑是不是在Q判定里面。
现象三:双向模式到边界之后不会反向。这个要重点看iDir变量的变化。如果按到边界后,iDir没有在等号的后面被赋值,说明你的IF判断写得有问题。可以用监控表盯着iDir,看看它在byIndex等于7的时候有没有变成-1。
现象四:仿真时一切正常,真机却偶尔丢一拍。这种情况在跑马灯项目里不太可能发生,但在真实项目里很常见,一般是任务周期太短,或者定时器精度不够。你可以把任务周期放宽试试,或者把定时器的PT改成1000ms,看丢拍现象是否消失。
5.3 一些来自实操的避坑经验
最后分享几个做跑马灯项目时容易忽略的经验。
第一,InoProShop仿真器对电脑性能有一些要求,尤其是老电脑。仿真时如果软件卡顿明显,把任务周期从10ms调到50ms,流畅度会好很多,对于跑马灯这种慢逻辑没有任何影响。
第二,如果你在仿真器里绑定了可视化变量,但是画面没有动静,先确认两件事:一是不是把可视化设置成了启动画面,二是不是PLC程序已经处于运行状态。我经常看到有人程序下载完忘记点运行,画面上当然什么都不会变。
第三,这个项目做真机调试时,如果PLC前面板有RUN/STOP拨码,记得拨到RUN位置再下载程序。有些人折腾半天发现程序不进,结果只是拨码停在STOP挡。
第四,变量命名最好统一风格。比如布尔变量用x开头,数值变量用n开头,数组变量用a开头,这个习惯在AM系列里特别重要。因为Codesys的智能提示对命名规范比较敏感,命名好了写代码速度快很多。
第五,真机调试千万别忘了先确认外部接线有没有短路。AM402的输出点虽然有过流保护,但接错线烧端子的事情我也不是没见过。上电之前用万用表量一遍,比出问题再去查划算多了。
6. 写在最后的经验之谈
跑马灯虽然只是一个入门案例,但我个人觉得它特别适合作为你进入Codesys生态的第一站。你通过这个项目掌握了Array、FOR循环、TON定时器、任务调度、可视化仿真,这五个点,其实就是以后所有复杂PLC程序的地基。后面你做轴控、做视觉联动、做数据采集,本质上都是这些基础模块的排列组合。
我自己的习惯是,每接触一个新的PLC品牌或新平台,都先拿跑马灯练一次手,顺便摸一遍软件的操作流程和坑。这也是为什么我一再建议你,不要只把程序跑通就算完,而是把ST、FBD、梯形图三种语言都各写一遍,把变速、双向、双灯这些扩展功能都做一遍。等你哪天不用看资料就能把这段程序写得行云流水,恭喜你,PLC的入门关算是真正过了。