专题介绍:用C语言编写应用层逻辑


基本信息

一般说来,梯形图特别适合逻辑控制程序的编写,因为支持在线监控和调试,现场修改也比较方便。但是梯形图不太适合写复杂的数学运算,例如通过循坏求平均值,通过迭代进行排序,亦或实现一个复杂的数学公式等。如果用梯形图来实现这些东西,可能需要很多很多的功能块,一方面看上去很不紧凑,另一方面效率也比较低,而且还需要占用很多临时变量来存储中间结果。针对这种情况,我们在KQD Application Editor中加入了C语言的支持。C语言的代码在下载时会直接编译成目标硬件的CPU指令,拥有超高执行的效率。C语言实现的计算可以被梯形图调用,C语言中也可以直接读写PLC的变量。

添加C语言子程序

这里举例说明如何在梯形图中添加C代码,在KQD Application Editor的【项目管理】中,右键点击【程序块】,在弹出菜单中选择【插入】子菜单的【Function (SBRx)】命令:

弹出的对话框中,选择插入的单元【SBR_1 (SBR1)】,点击确定:

弹出的对话框中,选择POU类型【NativeC】,点击确定:

即可进入【SBR_1 (SBR1)】的C代码编程界面:

C语言中读写PLC变量

在C语言中,不能直接使用PLC变量来进行运算,这是因为PLC变量一般使用大端模式(高位字节存储在低地址),而C语言中采用的是CPU的默认模式,一般为小端(低位字节存储在低地址)模式,所以必须通过特殊函数来读写PLC变量。假设我们希望在SBR1中实现PLC变量MW20的自加,需要按照下面的方式来书写:

其中GetU16表示从PLC中获取一个无符号16位整数,第1个参数M表示在PLC的M区域中取,第2个参数表示偏移量为20,返回的结果即为存储在MW20的无符号16位整数值。
其中SetU16表示向PLC中写入一个无符号16位整数,第1个参数M表示在PLC的M区域写入,第2个参数表示偏移量为20,第3个参数表示写入的值为C语言中的临时变量v。

我们在主循环中调用此C语言编写的SBR子程序,即可实现PLC变量MW20的自加过程:

全部的PLC变量读函数如下:

函数说明
GetU8(R, O)获取8位无符号数据
GetU16(R, O)获取16位无符号数据
GetU32(R, O)获取32位无符号数据
GetS8(R, O)获取8位有符号数据
GetS16(R, O)获取16位有符号数据
GetS32(R, O)获取32位有符号数据
GetT(R, O, B)获取1位数据

全部的PLC变量写函数如下:

函数说明
SetU8(R, O, V)写入8位无符号数据
SetU16(R, O, V)写入16位无符号数据
SetU32(R, O, V)写入32位无符号数据
SetS8(R, O, V)写入8位有符号数据
SetS16(R, O, V)写入16位有符号数据
SetS32(R, O, V)写入32位有符号数据
SetT(R, O, B, V)写入1位数据

参数说明

  • R:PLC变量区域,支持:I、Q、M、S、SM、V、T、C、HC、AI、AQ、L、AC。
  • O:PLC变量的偏移,字节单位。
  • B:PLC变量的位偏移。
  • V:PLC变量的值。

如果确实需要PLC变量的地址,可以使用PLC变量的外部声明来获取,例如上面的代码可以写成:

注意这里做运算之前需要进行大小端的转换。

全部的PLC变量外部声明如下:

extern uint8 Mb_I[]
extern uint8 Mb_Q[]
extern uint8 Mb_M[]
extern uint8 Mb_S[]
extern uint8 Mb_SM[]
extern uint8 Mb_V[]
extern uint8 Mb_T[]
extern uint8 Mb_C[]
extern uint8 Mb_HC[]
extern uint8 Mb_AI[]
extern uint8 Mb_AQ[]
extern uint8 Mb_L[]
extern uint8 Mb_AC[]

使用数学函数

因为最终需要将C代码编译成目标硬件CPU可以执行的指令,支持的数学函数完全依赖于编译器的实现。目前KQD Application Editor自带的编译器有两款:

  • ARM平台:gcc-arm-none-eabi-7-2017-q4-major-win32
  • ESP32平台:xtensa-esp32-elf-2021r2-8.4.0

只需要在C代码的头部包含【math.h】头文件,即可使用编译器提供的数学函数库,如果目标CPU支持硬件浮点,KQD Application Editor会根据目标硬件的CPU开启硬件浮点功能。

带参数的SBR实现公式计算

虽然我们可以通过C语言来直接读写PLC变量,但是为了接口更为清晰,一个更好的办法就是利用SBR的输入输出参数来封装算法。这里我我们以PT100的温度计算为例,已知PT100在0°以上的计算公式:

Rt1 = R0 * (1 + A * t1 + B * t1 * t1)

其中:

  • R0:热电阻在0度的阻值,一般为100欧。
  • A:系数,3.9083e-3。
  • B:系数,-5.775e-7。
  • t1:当前温度。
  • Rt1:当前电阻值。

修改SBR1的关联变量如下:

我们创建了一个INPUT变量Res,数据类型是REAL,地址在LD0。还创建了一个OUTPUT变量Temp,数据类型是REAL,地址在LD4。希望SBR1可以通过当前的电阻值Res,换算出当前的温度值Temp。

这里我们使用牛顿迭代法获得公式的近似解:

因为这里使用了fabs来求绝对值,所以需要包含数学库math.h。这里通过GetF32函数从临时变量LD0获取输入电阻;完成计算后通过SetF32函数将结果写入LD4输出温度。

在主程序中调用SBR1,下载程序后进行监控,电阻Res输入120,即可得到当前温度51.5661:

下载此梯形图程序

定义全局变量或常量

可以在SBR中定义全局的变量或常量:

这里我们定义了一个全局的数组用于存放每个水泵的高效流量范围。因为数组pumpQ的定义前面有const关键字,数组会被放入FLASH不占用RAM空间。还SBR内部我们可以通过索引来查询每个水泵的高效流量范围。

修改SBR1的关联变量如下:

我们创建了一个INPUT变量PIndex,数据类型是USINT,地址在LB0。还创建了一个OUTPUT变量QLow,数据类型是UINT,地址在LW1。还创建了一个OUTPUT变量QHigh,数据类型是UINT,地址在LW3。希望SBR1可以通过当前的水泵索引PIndex,换算出当前水泵的高效流量范围QLow和QHigh。

在主程序中调用SBR1,下载程序后进行监控,水泵索引PIndex输入2,即可得到当前水泵的高效流量范围QLow和QHigh:

下载此梯形图程序

C语言中调用梯形图指令

基于FCC的KQD Application Editor编程系统实际上是一个编译型系统,应用逻辑最终都会编译成目标硬件的CPU指令来执行。在FCC系统中,用梯形图编写的SBR,最终也是需要转换成C代码,然后和用C代码编写的SBR一起编译。对于链接程序来说,用梯形图编写的SBR和用C代码编写的SBR是完全等价的。

基于开发板DK60-STM32F412RE的PLC类型,我们在【SBR_0 (SBR0)】中写入以下梯形图:

点击编译,然后在KQD Application Editor的程序目录中,找到【KQDSoftwarePack\1.0.x\User\Functionals\SMX\Temp】目录下的【Fcc_POU_ECxx.c】文件:

即可看到梯形图编写的SBR0转换的C代码。

我们具体来看第一个网络:

Fcc_EC40_LD(QMakeBitBand(K_Mb_SM + 0, 0))LD指令,载入SM0.0。
Fcc_param[0].imm.W[0] = 1标记指令执行的位置。
Fcc_param[1].imm.B[0] = (Fcc_PU8Type)0x00设置指令的第1个操作数Port:Socket索引W5500AS0,即索引0。
Fcc_param[2].imm.W[0] = (Fcc_PU16Type)0x0BBA设置指令的第2个操作数IpPort:本地端口0x0BBA,即3002。
Fcc_param[3].ptr = (uint8*)(Fcc_PU8Type*)&Mb_V[20]设置指令的第3个操作数Status:VB20,因为是输出操作,这里设置地址。
Fcc_ExcuteInstruction(25)最后执行Udp_Bind指令,Udp_Bind在本PLC类型的指令号为25。

通过观察自动生成的代码可以发现,对于运算类型的代码,例如LD指令,直接带参数调用Fcc_EC40_LD函数即可。对于PLC系统指令,例如Udp_Bind指令,并不是简单的运算,必须由PLC系统来完成,则通过Fcc_ExcuteInstruction函数来实现,不同的PLC系统指令通过指令号来区分。如果是调用PLC系统指令,需要通过一个全局变量Fcc_param来进行参数的交换。

Fcc_param的定义在【Fcc_Priv.h】文件中实现:

可见最多可以传递16个参数给指令。如果是输入参数,需要在imm联合体中设置对应的常数。如果是输出参数,则需要在ptr中给出输出地址。

这里我们新建一个NativeC的SBR1,将SBR0的自动生成的C代码部分拷贝到SBR1中去,让SBR1完成SBR0同样的功能。我们在SBR1的代码上稍作修改,完成一个简单的变量应答功能,当以太网的UDP的端口收到变量字符串时,通过字符串形式发送变量的值:

将此程序下载到开发板DK60-STM32F412RE,在状态表中设置VW10的值:

打开TCP&UDP测试工具,通过UDP连接到开发板的3002端口,发送字符串【VW10】,则可以看到开发板返回的值【4455】

下载此梯形图程序