三菱 PLC 编程入门 3
目录
上一篇文章《三菱 PLC 编程入门 2》实现了跑马灯的例子。但是代码的灵活性不高。本文换一种实现,使得代码更容易测试和维护。

微信扫码查看视频
先分析问题。要实现跑马灯,就是对每个灯,做开和关的操作。即,何时开,何时关。
我们把这个操作抽象成一个独立的功能(函数块)。
LightOnOff
功能说明
- 在
onTime
至offTime
时段内,输出 True
参数说明
IN
: bool类型,功能开关,True – 功能生效;False – 计时器重置onTime
: time类型,打开时间offTime
: time类型,关闭时间Q
: bool 类型,输出
功能实现
// 函数块:LightOnOff
// 实现
IF NOT IN THEN
// 重置计时器
setOn(IN := FALSE); // TON(延时开)实例
setOff(IN := FALSE); // TP(持续开)实例
RETURN;
END_IF;
IF Q = FALSE THEN
// 如果灯灭,就延时打开
setOn(IN := TRUE, PT := onTime, Q := Q);
setOff(IN := FALSE); // 重置 TP(后续可以关灯)
ELSE
// 如果灯亮,就延时关闭
duration := offTime - onTime;
setOff(IN := TRUE, PT := duration, Q := Q);
setON(IN := FALSE); // 重置 TON(后续可以开灯)
END_IF;
为了方便调用,把这个函数块做个封装。
LightsOF
功能说明
- 输入灯的「编号」,实现对应的开关操作;
- 通过调用
LightOnOff
实现对应的操作。
参数说明
id
: int类型,灯的编号 0 到 7IN
: bool类型,输入onTime
: time类型,打开时间offTime
: time类型,关闭时间Q
: bool 类型,输出
功能实现
// 函数块:LightsOF
// 实现
CASE id OF
// LightOnOff 实例
0: LightOnOff0(IN := IN, onTime := onTime, offTime := OffTime, Q := Q);
1: LightOnOff1(IN := IN, onTime := onTime, offTime := OffTime, Q := Q);
2: LightOnOff2(IN := IN, onTime := onTime, offTime := OffTime, Q := Q);
3: LightOnOff3(IN := IN, onTime := onTime, offTime := OffTime, Q := Q);
4: LightOnOff4(IN := IN, onTime := onTime, offTime := OffTime, Q := Q);
5: LightOnOff5(IN := IN, onTime := onTime, offTime := OffTime, Q := Q);
6: LightOnOff6(IN := IN, onTime := onTime, offTime := OffTime, Q := Q);
7: LightOnOff7(IN := IN, onTime := onTime, offTime := OffTime, Q := Q);
END_CASE;
这么做的好处是,在主程序中可以用 FOR
语句,用遍历的方式对灯操作。
接下来计算灯的打开和关闭时间,即可实现跑马灯的逻辑。
// 延迟时间,单位:ms
iDelay := 500;
// 灯数
iNum := 8;
// 跑马灯效果:依次亮灯、依次灭灯
FOR ii := 0 TO iNum DO
// 灯 ii 的打开时间
onTime := INT_TO_TIME(iDelay * (ii + 1));
// 灯 ii 的关闭时间
offTime := INT_TO_TIME(iDelay * (2 * iNum - ii));
// 操作生效
// opLightsOF 是 LightsOF 的实例
// Lights 是数组,代表灯的状态(赋值给 Y 即可实现灯的开和关)
opLightsOF(id := ii, IN := TRUE, onTime := onTime, offTime := offTime, Q := lights[ii]);
END_FOR;
注意,上面代码只实现了跑马灯的逻辑。倒数第二行 opLightsOF
输出到 lights
,它是一个数组,代表灯的状态。
接下来需要把 Lights
输出到 Y
,即可实现跑马灯的效果。
我们把输出部分也独立成一个函数块。
InitLights
// 函数块:InitLights
// 实现
// lights 是数组(已声明)
Y0 := lights[0];
Y1 := lights[1];
Y2 := lights[2];
Y3 := lights[3];
Y4 := lights[4];
Y5 := lights[5];
Y6 := lights[6];
Y7 := lights[7];
别忘了重置计时器。
// 重置计时器
// resetter 是 TON 的实例
// isReset 是重置标签,TRUE - 执行重置,FALSE - 不执行重置
// 保证每个周期(iDelay * iNum * 2 毫秒)重置一次状态
resetter(IN := TRUE, PT := INT_TO_TIME(iDelay * iNum * 2), Q := isReset);
IF isReset THEN
// 重置 resetter 自身
resetter(IN := FALSE);
FOR ii := 0 TO iNum DO
// 重置每个灯的计时器
// opLightsOF 是 LightOF 的实例
opLightsOF(id := ii, IN := FALSE);
END_FOR;
END_IF;
完整的主程序如下。
// 配置 Configuration
// 延迟时间,单位:ms
iDelay := 500;
// 灯数
iNum := 8;
// 实现 Implementation
// 初始化
// runInitLights 是 InitLights 的实例
runInitLights(lights := lights);
// 跑马灯效果
FOR ii := 0 TO iNum DO
// 灯 ii 的打开时间
onTime := INT_TO_TIME(iDelay * (ii + 1));
// 灯 ii 的关闭时间
offTime := INT_TO_TIME(iDelay * (2 * iNum - ii));
// 操作生效
// opLightsOF 是 LightsOF 的实例
// Lights 是数组,输出到 Y
opLightsOF(id := ii, IN := TRUE, onTime := onTime, offTime := offTime, Q := lights[ii]);
END_FOR;
// 重置计时器
// resetter 是 TON 的实例
// isReset 是重置标签,TRUE - 执行重置,FALSE - 不执行重置
// 保证每个周期(iDelay * iNum * 2 毫秒)重置一次状态
resetter(IN := TRUE, PT := INT_TO_TIME(iDelay * iNum * 2), Q := isReset);
IF isReset THEN
// 重置 resetter 自身
resetter(IN := FALSE);
FOR ii := 0 TO iNum DO
// 重置每个灯的计时器
// opLightsOF 是 LightOF 的实例
opLightsOF(id := ii, IN := FALSE);
END_FOR;
END_IF;
最后,做个总结。程序可分为三个模块:
- 初始化:作用是绑定输出,对应函数块
InitLights
。 - 开关操作:实现定时开和定时关的逻辑,对应函数块
LightsOnOff
和LightOF
,其中前者是功能实现,后者是对前者的封装。 - 主程序:实现完整功能,它调用上述两个模块。
模块化就是解耦,有两个好处:一是提升开发效率,可以多人一起开发;二是方便测试和维护,各模块独立测试。