许多 PLC 程序员都有使用 Arduino 的经验
许多PLC程序员是通过完成微控制器课程开启电子技术之旅的你的第一个程序可能是经典的"hello world"或让Arduino连接的LED灯闪烁这些技能为看似无关的编程环境(包括可编程逻辑控制器PLC)奠定了绝佳基础
我们将通过从Arduino的map()函数过渡到类似的梯形图逻辑(LL)映射函数,来运用这些先验知识在PLC内部,这个映射函数将作为用户自定义功能块(UDFB)实现我们的讨论基于广受欢迎的施耐德电气Modicon PLC展开本课程适用于大多数LL编程的PLC,但具体实现细节会有所不同
该应用的测试平台如图1所示图中可见施耐德TM221CE24T Modicon PLC驱动Banner K50 Pro触摸按钮。
图 1 :施耐德Modicon PLC驱动Banner K50 Pro触摸按钮
将 Arduino 技能应用于 PLC
在深入PLC实现之前,我们先通过Arduino视角探讨三个基本概念包括:
- 函数组成要素回顾
- map()函数的数学原理
- 最后是点表示法的概念
回顾这些C语言编程概念后,你会发现这些基础要素可直接应用于基于Modicon的UDFB或许你会认同,梯形图逻辑与C语言的相似度超出你的最初想象将来你会发现,这种拓展的视角为使用结构化文本进行PLC编程奠定了完美基础
函数组成要素
请记住,函数用于封装一组C语句或许你曾编写过让LED闪烁N次的Arduino函数记得这个函数可能命名为blinkLED它可能包含N等参数,用于指定LED闪烁次数你可能已经为LED的开启和关闭时间传递了其他参数。你甚至可能已经对I/O引脚进行了参数化设置。
void LEDblink(int pin, int N, long msOnTime, long msOffTime) {
pinMode(pin, OUTPUT);
for (int i = 0; i < N; i++) {
digitalWrite(pin, HIGH);
delay(msOnTime);
digitalWrite(pin, LOW);
delay(msOffTime);
}
}
技术提示 :虽然C语言函数只能有一种返回类型,但我们可以使用间接寻址来扩展参数数量。对于间接寻址,我们将数组或结构的地址传递给函数。我们的函数可以访问数组或结构中的所有元素。元素总数受限于相关微控制器的内存和架构。
结构与点号表示法
在转向PLC之前,我们应该探讨C编程中使用的点号表示法。请考虑以下代码:
typedef struct { // Structure definition
float minVoltage;
float maxVoltage;
float deviation;
} Status;
Status CellStatus; // instance of the structure
注意最后一行代码创建了一个Status结构的实例。该结构包含minVoltage、maxVoltage和deviation字段。要访问结构中的各个字段,我们使用点号表示法。例如:
CellStatus.minVoltage = 13.8;
float maxVoltage = CellStatus.maxVoltage;
每种情况下,我们看到结构名称后跟一个点号,然后是感兴趣的字段。
Arduino map() 函数
在讨论PLC UDFB之前,我们还有最后一个概念需要探讨。具体来说,在PLC中实现之前,我们先从Arduino的角度探讨map函数的数学运算。
Arduino map()函数是与直线方程相关的代数应用:
y = mx + b (斜截式)
y – y_1 = m(x – x_1) (点斜式方程)
在Arduino中,你可能使用map()函数来缩放来自Arduino模数转换器(ADC)的模拟输入。
int rawADCval = analogRead(0);
int HumanReadableVoltage = map(rawADCval, 0, 1023, 0, 5); //Assume a 5 VDC ADC reference
简单的说,这表示ADC输出0到1023映射到0到5伏直流电压。然而,存在一个大问题,因为只允许0、1、2、3、4和5伏的电压。这是整数数据类型的限制,稍后将使用float类型暂时修正。
map()函数本身定义为:
long map(long x, long in_min, long in_max, long out_min, long out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
仔细检查map()函数显示它实现了点斜式方程:
y \ – y_1 = m(x\ – x_1)
其中
m = \dfrac{out\_max - out\_min} {in\_max - in\_min}
m = \dfrac{输出\_最大值 - 输出\_最小值} {输入\_最大值 - 输入\_最小值}
得到
y = \dfrac{输出\_最大值 - 输出\_最小值}{输入\_最大值 - 输入\_最小值} (x - 输入\_ 最小值) + 输出\_最小值;
修改 Arduino 的 map 函数以支持浮点数
个人而言,我更倾向于构建一个使用浮点类型运算的函数。只需如下所示更改函数数据类型即可轻松实现。这一微小改动消除了前文提到的整数运算问题。
float fmap(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
float rawADCval = analogRead(0);
int HumanReadableVoltage = fmap(rawADCval, 0.0, 1023.0, 0.0, 5.0);
Modicon 用户自定义功能块 (UDFB) 简介
至此,我们已经回顾了与映射(缩放)功能相关的基础编程概念,包括:
- 函数的组成部分
- 函数组件的命名
- 结构体
- 数据类型的限制
- 点表示法
- 点斜式方程
现在我们可以为Modicon PLC构建基于梯形逻辑的UDFB了。UDFB 如图 2 所示。UDFB定义包含两个部分。图2下方显示了UDFB名称、布尔输入及参数。梯形逻辑呈现在图2的上半部分。
图 2 :Modicon FMAP用户自定义功能块。
启用 (EN) 和启用输出 (ENO) 用于将 UDFB 悬挂在梯级上
显然,所有梯形逻辑元件都"悬挂在梯级上",如图3梯级#2中FMAP UDFB的实例所示。这一重要操作在图2下半部分确立(定义)。此处我们看到启用(EN)和启用输出(ENO)配对。在图2梯级#中可见ENO是EN信号的副本。换言之,若存在EN信号,则该功能块的ENO信号将判定为真。未来我们可以将多个此类功能块置于同一梯级上,每个下游功能块都能接收到使能信号的副本。
技术提示 :UDFB关联着多组不同的"悬空"输入输出对。
本例展示的是传统的EN/ENO信号对。仅当功能块被使能且ENO信号直接复制EN信号时,该功能块才会工作。
电平敏感 信号对包含EN信号和VALID信号。仅当功能块被使能时才会工作。当UDFB完成指定操作时,VALID信号将被置位。方向控制阀(DCV)就是个典型例子。UDFB在接收到EN信号时会触发DCV切换。当被监测气缸运动到正确位置后,VALID信号会在稍后被置位。通常还会包含BUSY输出信号。
边沿敏感 型UDFB包含触发(TRIG)和完成(DONE)信号对。UDFB在收到TRIG输入信号后将执行指定任务。任务完成后,UDFB会发出DONE输出脉冲。与电平敏感型UDFB类似,边沿敏感型UFDB也具备BUSY输出信号。此外,通常还设有ABORT或RESET输入来撤销TRIG信号。以"西蒙说"游戏为例说明。边沿敏感型UDFB可用于计算机回放环节。完成后可触发另一个边沿敏感型UDFB进行用户回放。
传统 UDFB 的运行基于 EN 信号。
EN信号用于启用UDFB的各种数学运算。图2所有梯级中都能看到这一点——每个梯级起始处都有EN触点。用专业术语来说,当且仅当通过EN线使能时,UDFB的输出才会更新。这可能令人困惑,因为存在隐式存储操作——当EN信号缺失时将保持最后的值。这是测试传统UDFB未更新的首要事项之一。
技术提示 :传统UDFB关联的隐式内存可能是有益的特性。它允许周期性脉冲短暂更新UDFB输出。例如,过程控制器中使用的UFB可被指令在每秒脉冲(PPS)信号上更新并保持其输出值。
UDFB 参数
图2展示了与UDFB关联的参数。术语"参数"可宽泛理解为与UDFB关联的内存结构。这种区分很重要,原因如下:
- 参数类型同时充当所有非布尔型UDFB输出的输入和输出。在本特定UDFB中,我们使用浮点类型进行所有线性方程计算。
- 如图4所示,通过点标记法访问"参数"。
- 当UDFB实例化时,每个实例都与内存关联。在底层,这类似于前文提到的"Status CellStatus;"结构。此操作也解释了每个UDFB如何拥有自己的参数和内部变量。
UDFB 数学运算
图2中的第1和第2梯级展示了本文前文探讨的FMAP函数的UDFB实现。注意我们使用的是双操作数数学运算。例如,输出差值(D_OUT)是OUT_MAX与OUT_MIN之间的差。同理D_IN = IN_MAX – IN_MIN。斜率(M)计算为D_OUT / D_IN。其余运算继续求解给定X输入时Y的斜截式方程。
如何实例化 UDFB
本文开篇我们介绍了Arduino。具体而言,我们研究了直线方程、函数组成部分及点标记法。至此,我们已成功将函数与UDFB进行了比较。我们还注意到C函数与UDFB在机制上的相似性,需理解的是UDFB对“参数”这一术语的使用更为自由,既指输入也指输出。最后,我们暗示了每个UDFB实例都关联着一个类似结构体的内存对象。现在可以实例化UDFB并使用点标记法了。
图3第2梯级展示了FMAP UDFB实际挂载在梯级上的实例。该UDFB由第0和第1梯级所示的操作块支持。例如:第1梯级首个操作块将常数值0.0载入%FMAP0.IN_MIN内存地址。您会注意到点运算符用于访问UDFB实例关联的成员(变量)。
基于这种标记法和内存结构,我们可以断言施耐德Machine Expert处理UDFB的方式与C++处理对象如出一辙。两种编程语言都实现了封装。
本例中 UDFB 的用途是什么?
该UDFB的完整功能是将Modicon PLC模数转换器(ADC)的数值转换为人类可读值。图3第0梯级显示ADC原始计数值为533。需知Modicon ADC配置为将0-10VDC满量程对应到0-1000。第3梯级可见UDFB右侧紧邻着操作块。注意该值已被缩放为浮点数值5.33。
确切地说,这个UDFB实例存在过度设计。确实本可采用简单的除以10操作块实现。但假设电压代表物理系统压力,1-7VDC对应30-90psi。这种情况下应使用两个FMAP UDFB。首个UDFB将信号转为人类可读格式。第二个UDFB再将可读浮点值映射为对应psi值。这种双模块结构最大程度提升了未来技术人员的可读性。例如技术员可用万用表在PLC面板读取实际电压值。随后可在PLC代码内查看对应数值。
技术提示 :多数PLC编程环境同时支持函数块(UDF)和用户自定义函数块(UDFB)。这些在封装方面是相似的,但在封装方面有所不同。每个UDFB的实例化都有自己的内存结构,而FB则没有。举个例子,如果我们使用FB,就无法实现之前提到的设置和记忆功能。
图 3 :FMAP UFDB挂在第2梯级上。比例参数使用点符号加载在第1梯级上。此外,原始ADC值也加载在第1梯级上。
技术提示 :实现UDFB时要格外小心。永远记住PLC的生命周期是以十年为单位计算的。多年以后,当最初的程序员早已不在时,技术人员和工程师们将对这些代码进行故障排除和修改。清晰的文档和单一用途是UDFB的基本属性。换句话说,编程要清晰明了。不要耍小聪明!
结论
用户定义功能块(UDFB)相当于PLC中的“C”函数。两者都提供了一种封装常用代码的方式,从而提供相对易于阅读的层次化代码结构。这抽象了底层,使程序员和未来的工业技术人员能够轻松地跟踪程序流程。这两种软件结构都能让程序员节省大量时间,因为他们可以从库中选择健壮且经过充分调试的软件。


