简介:这份资源是面向欧姆龙PLC编程初学者与现场调试人员的中文ST语言手册,针对功能块编程入门与规范操作需求,帮助读者理解结构化文本在欧姆龙控制系统中的实际用法。压缩包内共1个PDF文件,约2.26MB,内容为CX-Programmer 6.1环境下的功能块操作手册中文版,涵盖功能块介绍与类型划分、变量声明、库文件转换、技术规格与实例规格、功能块限制等章节,并附安全注意事项与应用注意事项说明,便于对照查阅。目前已有7311人学习下载,说明其在工控学习者中具有一定参考价值。读者可从中获得功能块基本单元与高级单元的区分方法、编程环境设置思路、基本语法要点以及安全规范提示,适合作为入门指南与日常编程参考,也可配合实际项目查漏补缺,提升编程效率与规范性。
1. 欧姆龙ST编程中文手册:一份被低估的PLC结构化文本落地资料
手上接过一个改造项目,前任工程师留下一堆梯形图,注释全是日文,功能块调用关系像蜘蛛网。想用ST重写,翻遍论坛只找到零散的指令表,直到有人丢来一份W447-CN5-02——欧姆龙CX-Programmer 6.1的功能块操作手册中文版。这不是那种“Hello World”式的入门教程,而是把功能块定义、变量作用域、实例规格、编译限制一条条写死的工程文档。它解决的核心问题很具体:当你需要在CS1-H、CJ1-H、CJ1M、CP1H这些CPU上把重复逻辑封装成功能块时,变量怎么声明、AT设定什么时候必须加、数组长度和指令操作数怎么对齐,手册里都有明确边界。适合已经能写梯形图、准备把项目往结构化文本迁移的电气工程师,也适合带学生做PLC课程设计、需要一份靠谱中文参考的导师。下面按我拆这份手册的顺序,把能直接抄的部分和容易翻车的地方摊开讲。
2. 功能块与变量:先搞清ST编程的底层约定
2.1 功能块不是函数,实例规格决定内存开销
很多人第一次接触欧姆龙ST,会把功能块当成高级语言里的函数,调一次算一次。翻到手册第2章“技术规格”才明白,功能块定义和实例是两回事。定义只描述算法和接口,实例才是真正占内存的运行时对象。手册里2-2节“实例规格”给了明确数字:每个实例在CPU单元里占固定字节数,跟内部变量数量直接挂钩。这意味着你在一个项目里反复调用同一个功能块,每调一次就多一份实例内存,不是共享的。
我一般会先算一笔账:假设一个功能块内部有10个WORD型内部变量、2个定时器实例,按手册2-7节“CPU单元的功能块规格”的对照表,单个实例大概吃掉几十字节。一个中型项目调了50次,就是几千字节的固定开销。CP1H的DM区本来就紧张,这一步不算清楚,后面在线修改时很容易撞上“实例数超限”的编译错误。
手册1-3节“变量”把变量分成内部变量、外部变量、输入变量、输出变量四类,这个分类直接决定编译行为。输入变量在算法执行前被批量赋值,输出变量在算法结束后批量写出,中间过程想读写同一个值,必须用内部变量过渡。这一点跟梯形图里直接操作CIO区地址的习惯完全不同,也是从梯形图转ST时最容易踩空的地方。
2.2 变量声明与AT设定:编译期就能拦住的错误
手册附录C“外部变量”和2-5节“规定多字中第一个字或最后一个字的操作数指令注意事项”是整份文档里信息密度最高的部分。核心规则一句话:当指令操作数要处理多个字,而你又想用变量去指定首地址或末地址时,变量必须带AT设定,或者变量本身的数据长度要和指令处理的数据长度一致。否则CX-Programmer在编译时直接报错。
下面这段ST代码是我按手册规则整理的变量声明模板,放在功能块定义里:
(* 功能块内部变量声明区,按手册1-3节分类 *) VAR_INPUT StartSig : BOOL; (* 启动信号,输入变量 *) SetValue : WORD; (* 设定值,输入变量 *) END_VAR VAR_OUTPUT DoneFlag : BOOL; (* 完成标志,输出变量 *) ActValue : WORD; (* 实际值,输出变量 *) END_VAR VAR (* 内部变量:需要AT设定,因为要被多字指令当操作数 *) AT %D100 : WorkBuf[10] : WORD; (* 数组,长度10,对应DM100~DM109 *) AT %D200 : CmpBuf : WORD; (* 单字,带AT *) EdgeMem : BOOL; (* 边沿记忆,普通内部变量 *) Timer1 : TIM; (* 定时器实例 *) END_VAR逻辑说明:WorkBuf声明为长度10的WORD数组并带AT %D100,意味着它固定映射到DM100开始的10个字。手册2-5节明确要求,当指令操作数指定多字首地址时,要么用AT设定,要么数组元素数量与指令处理长度严格相等。这里选AT设定,是因为后面要调用一个处理10个字的块比较指令,用AT能保证地址连续且编译期可检查。CmpBuf带AT %D200,是为了给单字比较指令一个确定的物理地址,避免变量被优化到临时区导致地址漂移。
参数说明:StartSig和SetValue是输入变量,在功能块被调用时由外部参数传入,算法内部只读。DoneFlag和ActValue是输出变量,算法结束后统一写出,中间过程不要试图在算法里读DoneFlag的当前值,读到的可能是上一周期的旧值。EdgeMem是普通内部变量,不映射物理地址,适合做上升沿检测。Timer1是定时器实例,每个功能块实例各有一份,不共享。
注意:手册2-5节特别提醒,如果数组元素数量小于指令操作数指定的长度,比如指令要处理16个字而数组只注册了10个元素,编译不会报错,但运行时会越界读写后面6个字。这6个字如果被别的指令占用,就是典型的“玄学故障”——程序逻辑没错,输出却乱跳。我现在的习惯是,凡是多字操作数,数组长度宁可多给两个元素,并在注释里写清楚实际使用范围。
2.3 功能块定义转库文件:复用前的最后一步
手册1-4节“将功能块定义转换为库文件”讲的是把调好的功能块存成库,供多个项目引用。这一步在CX-Programmer里的操作路径是:在项目树里右键功能块定义,选“创建库文件”,指定保存路径和库名。生成的库文件扩展名是.cxl,下次新建项目时通过“插入库”加载。
这里有个容易被忽略的细节:库文件里保存的是功能块定义,不包含实例。也就是说,你把库插到新项目里,还得手动创建实例并分配变量。手册1-4节末尾提了一句“库文件中的功能块定义不可直接编辑”,要改必须先在项目里实例化、修改、再重新生成库。我一般会在库文件名里带上版本号和日期,比如MotorCtrl_v2_202406.cxl,避免不同项目引用了不同版本的库却看不出来。
3. 从梯形图迁到ST:创建功能块的实操路径
3.1 程序流与任务配置:先定框架再写算法
手册第3章“创建功能块”开篇给了一张程序流图,3-1节把从新建项目到下载运行的步骤串了一遍。按我的实操顺序,第一步不是急着写ST代码,而是先把任务结构定下来。CX-Programmer 6.1里,功能块可以放在循环任务或中断任务里调用,但手册2-3节“功能块限制”明确写了:包含功能块的任务无法在任务单元中下载,上传可以。这意味着如果你用了任务单元做分布式管理,功能块任务得单独处理。
我一般会新建一个循环任务专门放功能块调用,周期时间设成和主任务一致,比如4ms。然后在功能块定义里只写算法,不碰I/O刷新。I/O映射统一放在主任务的梯形图段里做,功能块的输入输出通过全局符号对接。这样做的原因是手册4节“应用注意事项”里提到,功能块内部的输入变量是批量赋值的,如果直接在功能块里读写物理I/O地址,时序上会和主任务的I/O刷新错开,轻则响应慢一拍,重则出现竞争。
3.2 ST语法骨架:条件、循环与边沿处理
手册附录B“结构化文本(ST语言)”给了语法参考,但篇幅有限,很多写法得结合附录A“数据类型”一起看。下面这段是我按手册语法规则写的一个电机启停控制功能块,包含自锁、互锁和故障复位,可以直接在CX-Programmer 6.1里编译通过:
(* 电机启停控制功能块,ST语言实现 *) (* 输入:启动、停止、故障反馈、复位 *) (* 输出:运行指令、故障锁存 *) VAR_INPUT StartPB : BOOL; (* 启动按钮,常开 *) StopPB : BOOL; (* 停止按钮,常闭,正常时为TRUE *) FaultFB : BOOL; (* 故障反馈,TRUE表示有故障 *) ResetPB : BOOL; (* 复位按钮 *) END_VAR VAR_OUTPUT RunCmd : BOOL; (* 运行指令输出 *) FaultLT : BOOL; (* 故障锁存输出 *) END_VAR VAR RunMem : BOOL; (* 运行记忆 *) FaultMem : BOOL; (* 故障记忆 *) StartEdge : BOOL; (* 启动上升沿 *) ResetEdge : BOOL; (* 复位上升沿 *) LastStart : BOOL; (* 上一周期启动信号 *) LastReset : BOOL; (* 上一周期复位信号 *) END_VAR (* 上升沿检测:当前为TRUE且上一周期为FALSE *) StartEdge := StartPB AND NOT LastStart; ResetEdge := ResetPB AND NOT LastReset; LastStart := StartPB; LastReset := ResetPB; (* 故障锁存:故障反馈有效时置位,复位按钮上升沿且无故障时复位 *) IF FaultFB THEN FaultMem := TRUE; ELSIF ResetEdge AND NOT FaultFB THEN FaultMem := FALSE; END_IF; (* 运行控制:启动上升沿置位,停止按钮断开或故障时复位 *) IF StartEdge AND NOT FaultMem AND StopPB THEN RunMem := TRUE; ELSIF NOT StopPB OR FaultMem THEN RunMem := FALSE; END_IF; (* 输出赋值 *) RunCmd := RunMem; FaultLT := FaultMem;逻辑说明:边沿检测用LastStart和LastReset两个内部BOOL做记忆,每个扫描周期更新一次。故障锁存优先于运行控制,FaultFB为TRUE时直接置位FaultMem,运行逻辑里NOT FaultMem条件不满足,RunMem被强制复位。停止按钮按常闭处理,正常时StopPB为TRUE,按下时变FALSE,NOT StopPB成立触发复位。复位按钮只在无故障时有效,避免故障未消除就强行复位。
参数说明:StartPB和ResetPB按常开按钮接法,上升沿触发。StopPB按常闭按钮接法,正常闭合时输入为TRUE,这个约定要在变量注释里写死,不然接线的人按常开接,逻辑就反了。FaultFB来自热继电器或驱动器故障输出,高电平有效。RunCmd和FaultLT是输出变量,在功能块算法结束后统一写出到外部参数,不要在算法中间去读RunCmd的当前值做判断,手册1-3节明确说了输出变量是批量写出的。
提示:手册附录B里ST的
IF...ELSIF...END_IF结构要求每个分支都有明确赋值,如果某个分支漏了赋值,变量保持上一周期值。上面代码里RunMem在两个分支都有赋值,不会出现保持。但FaultMem的ELSIF分支只在ResetEdge AND NOT FaultFB时执行,其他情况保持,这是故意的锁存行为。
3.3 编译与下载:BKUP灯亮时别断电
代码写完,编译通过只是第一步。手册4节“应用注意事项”里有一条血泪经验:执行在线编辑后,CS1-H、CJ1-H、CJ1M和CP1H CPU单元会把用户程序和参数区数据备份到内置闪存,备份期间CPU前面的BKUP显示灯亮。灯亮时关电源,数据不备份,下次上电可能回到旧程序。
我现在的流程是:编译通过→下载→看BKUP灯→等灯灭→再切运行模式。下载前还会做一件事,在PLC属性里勾选“显示PLC存储器备份状态”,这样在线编辑后能直接在CX-Programmer里看到备份进度,不用盯着CPU面板。手册里提到这个查看路径是“显示”对话框→PLC属性→PLC存储器备份状态,然后从查看菜单选Windows→PLC存储器备份状态。
另一个容易翻车的点是下载目标。手册明确写了,包含功能块的用户程序下载到不支持功能块的CPU单元(2.0版及以下的CS/CJ系列)时,所有实例被视为非法指令,程序无法编辑或执行。手上如果有老设备,下载前先在CX-Programmer的设备型号里确认CPU版本,别等下载完报一堆非法指令再回头查。
4. 避坑与排查:五个真实踩过的坑
4.1 现象:编译通过,运行后输出乱跳
原因:数组变量长度小于指令操作数指定的长度。手册2-5节写了,当另一个操作数指定长度的是变量而非常量时,6.1版CX-Programmer编译不报错,只给警告。运行时指令对超出数组元素数的部分照常读写,踩到别的变量头上。
解决:凡是多字指令的操作数,数组长度按最大可能长度声明,并在变量注释里写清楚实际使用范围。我现在的习惯是数组长度比实际需要多给2到4个元素,留出余量。如果长度由变量动态指定,在功能块入口加一段范围检查,超限直接置故障标志,不让指令执行。
4.2 现象:功能块里读输出变量,值总是慢一拍
原因:手册1-3节和4节都提到,输入变量在算法执行前批量赋值,输出变量在算法结束后批量写出。在算法中间读输出变量,读到的是上一周期的值,不是当前计算结果。
解决:需要中间过程参与判断的量,一律用内部变量。输出变量只在算法最后赋值一次,中间逻辑全部走内部变量。上面电机控制代码里RunMem和FaultMem就是内部变量,RunCmd和FaultLT只在末尾赋值,就是这个原因。
4.3 现象:在线编辑后断电重启,程序回到旧版本
原因:BKUP灯亮期间断电,闪存备份没完成。手册4节明确警告过这个行为。
解决:在线编辑后等BKUP灯灭再断电。如果现场不方便盯面板,在CX-Programmer里打开PLC存储器备份状态窗口,看到备份完成再操作。另外,下载前确认CPU单元型号支持功能块,2.0版及以下的CS/CJ系列不支持,下载了也跑不起来。
4.4 现象:功能块调用次数多了,编译报实例数超限
原因:每个功能块实例占固定内存,调用次数乘以单实例开销,超过CPU单元的功能块规格上限。手册2-7节给了各CPU型号的功能块规格表,CP1H和CJ1M的上限不一样。
解决:先按手册2-2节实例规格算总开销,再决定哪些逻辑合并成一个功能块、哪些拆开。重复度高的简单逻辑(比如十几个一样的阀门控制)可以合并成一个功能块,用数组参数批量传入,减少实例数。实在不够,把部分功能块改成梯形图子程序,梯形图不占功能块实例内存。
4.5 现象:输入参数填了数值,功能块里读出来是地址值
原因:手册4节“应用注意事项”里有一条,输入变量数据不是布尔格式且参数中只输入数值时,CIO区地址的实际值会被略过。比如输入20,实际读到的是0020这个地址里的内容,不是数值20。
解决:输入数值前加前缀,&20表示十进制常数,#20表示十六进制常数,+20或-20表示带符号常数。这个规则在手册里写得比较散,我一般在新人上手时直接把这条贴在工位上。另外,地址本身不能当输入变量略过,要在功能块里用带AT设定的内部变量去接。
5. 进阶用法:用外部变量和库文件管住大型项目的变量命名
手册附录C“外部变量”是我认为整份文档里最值得反复看的部分,但很多人翻到附录就跳过了。外部变量的作用是让功能块内部能引用全局符号,同时保持功能块定义的独立性。具体做法是:在功能块定义里声明外部变量,变量名和全局符号一致,CX-Programmer在编译时自动关联。这样功能块搬到另一个项目,只要全局符号名不变,不用改功能块内部代码。
我一般会建一个命名规范表,放在项目根目录的说明文档里,格式如下:
| 变量类型 | 前缀 | 示例 | 说明 |
|---|---|---|---|
| 外部变量 | g_ | g_Motor1Run | 全局符号,功能块内引用 |
| 输入变量 | i_ | i_StartPB | 功能块输入参数 |
| 输出变量 | o_ | o_RunCmd | 功能块输出参数 |
| 内部变量 | v_ | v_RunMem | 功能块内部使用 |
| AT设定变量 | a_ | a_WorkBuf | 固定映射物理地址 |
这张表不是手册里的,是我按手册1-3节和附录C的分类自己定的。好处是功能块定义转成库文件后,换项目引用时一眼能看出哪些变量依赖外部全局符号,哪些是自包含的。库文件里的功能块如果引用了外部变量,插入新项目前先把全局符号表建好,否则编译报未定义符号。
验证方法也简单:新建一个空项目,插入库文件,编译。如果报外部变量未定义,说明全局符号没建全;如果编译通过但下载后行为不对,用CX-Programmer的观察窗口监控外部变量的当前值,看是不是被别的任务改写了。手册4节提醒过,分配到基本I/O单元、特殊I/O单元和CPU总线单元的地址无法注册为全局符号,这些地址不能做外部变量,只能走AT设定的内部变量。
从那以后我每次把功能块转库文件之前,都强制走一遍“空项目插入编译”的验证,确认外部变量依赖清单完整再归档。这份手册的价值不在于它教了多少语法,而在于它把编译期能拦住的错误和拦不住只能靠人盯的错误分得很清楚,照着边界写,少熬几个通宵。希望帮到你。
本文还有配套的精品资源,点击获取