306 lines
12 KiB
Markdown
306 lines
12 KiB
Markdown
# 控 - 控制框架
|
||
|
||
工业生产中控制逻辑的复杂程度千变万化,往往需要具体行业专业人员完成专门的设计,从而提高了行业的技术壁垒,严重阻碍了工业领域的自动化和智能化升级。
|
||
|
||
XiUOS 应用程序框架中的“控”子框架从“控制需求”本身出发,同时面向专业用户和非专业用户,通过对“控制需求”本身和复杂的工业控制流程进行深入分析,通过软件定义的方式,提出以“控制元”为核心的“控”制流程。具体地,本设计通过解耦复杂的工业控制流程,将工业生产中的各种复制工业控制流程分解为各种类型的“控制元”命令,这些“控制元”的命令以软件API的形式交互给用户使用,从而屏蔽了以 PLC 为中心的各种控制器的巨大差异,形成了方便易用的接口,降低了专业的技术壁垒,加速了工业领域的智能化升级。
|
||
|
||
## 1. XiUOS“控”制框架的关键数据结构定义和解析
|
||
|
||
```c
|
||
struct xs_PlcAbility {
|
||
const char name[XS_NAME_MAX]; /* name of the PLC ability instance */
|
||
enum xs_PlcCtlType type; /* type of control the plcable to excute, such as HSC or PID control */
|
||
char address[XS_PLC_ADDRESS_MAX]; /* The address for this function in the PLC*/
|
||
struct xs_PlcDevice *pdev; /* corresponding PLC device */
|
||
struct XS_DOUBLE_LINKLIST_NODE link;/* link list node */
|
||
};
|
||
```
|
||
|
||
name成员是一个可读的名字,用于唯一标识一个xs_PlcAbility结构。
|
||
type成员表示该xs_PlcAbility可控制的类型,用一个枚举变量表示:
|
||
|
||
```c
|
||
enum xs_PlcCtlType {
|
||
XS_PLC_CONTROL_TYPE_HSC = 0, /* high-speed counter */
|
||
XS_PLC_CONTROL_TYPE_PID , /* proportional,integral,derivative */
|
||
XS_PLC_CONTROL_TYPE_PHASING , /* phasing control*/
|
||
|
||
/* ...... */
|
||
XS_PLC_CONTROL_TYPE_END,
|
||
};
|
||
```
|
||
|
||
由于 PLC 里控制指令执行都是向数据模块DB写数据,需要知道该函数功能对应的数据块地址,这个用address标识。pdev成员表示该xs_PlcAbility所属的xs_PlcDevice结构,其具体定义在下文给出。 最后,在系统中不同控制类型的xs_PlcAbility被分别组织成不同的双链表,如高速计数器xs_PlcAbility链表、PID控制xs_PlcAbility链表等,使用的链表节点即为link成员。
|
||
|
||
```c
|
||
struct xs_PlcDevice {
|
||
const char name[XS_NAME_MAX]; /* name of the device */
|
||
struct xs_PlcInfo info; /* PLC info, such as vendor name and model name */
|
||
struct xs_PlcOps ops; /* filesystem-like APIs for data transferring */
|
||
struct xs_PlcInterface interface; /* protocls used for transferring data from program to PLC */
|
||
structXS_DOUBLE_LINKLIST_NODE link; /* link list node */
|
||
};
|
||
```
|
||
|
||
name成员记录 PLC 设备在系统中的名字,用于唯一标识一个xs_PlcDevice结构。
|
||
|
||
```c
|
||
struct xs_PlcInfo {
|
||
uint32_t ability;
|
||
const char *vendor;
|
||
const char *product_model;
|
||
};
|
||
```
|
||
|
||
info成员记录 PLC 设备的一些属性信息,包括 PLC 的能力ability、厂家名vendor与型号product_model,其中ability用一个位图表示该 PLC 设备可以控制进行的操作:
|
||
|
||
```c
|
||
#define XS_PLC_ABILITY_HSC ((uint32_t)(1 << XS_CONTROL_TYPE_HSC))
|
||
#define XS_PLC_ABILITY_PID ((uint32_t)(1 << XS_CONTROL_TYPE_PID))
|
||
#define XS_PLC_ABILITY_Phasing ((uint32_t)(1 << XS_CONTROL_TYPE_PHASING))
|
||
/* ...... */
|
||
```
|
||
|
||
ops成员包含统一的、类似文件系统的API,用于和 PLC 设备通信,进行实际的数据读写和对 PLC 设备实现控制功能。在使用一个 PLC 设备前后需要打开(open)/关闭(close)该 PLC,实际为建立和关闭连接;read、write分别用与从 PLC 接收数据与向 PLC 发送数据,ioctl用于向 PLC 设备发送控制指令:
|
||
|
||
```c
|
||
structxs_PlcOps {
|
||
int (*open)(struct xs_PlcDevice *pdev);
|
||
void (*close)(struct xs_PlcDevice*pdev);
|
||
int (*read)(struct xs_PlcDevice*pdev, void *buf, size_tlen);
|
||
int (*write)(struct xs_PlcDevice*pdev, constvoid *buf, size_tlen);
|
||
int (*ioctl)(struct xs_PlcDevice*pdev, intcmd, void *arg);
|
||
};
|
||
```
|
||
|
||
interface成员表示用于与 PLC 进行通信时所用到的协议:
|
||
|
||
```c
|
||
struct xs_PlcInterface {
|
||
enum xs_plc_protocol protocol;
|
||
enum xs_plc_transport transport;
|
||
};
|
||
```
|
||
|
||
xs_plc_protocol和xs_plc_transport是两个枚举类型,标识 PLC 设备和自研的两种终端之间用到的通讯协议,其定义如下:
|
||
|
||
```c
|
||
enum xs_plc_protocol{
|
||
AB_ETH = 0,
|
||
ADS_AMS,
|
||
BACnet_IP,
|
||
DeltaV,
|
||
DF1,
|
||
EtherNet_IP,
|
||
Firmata,
|
||
KNXnet_IP,
|
||
Modbus,
|
||
OPC_UA,
|
||
S7,
|
||
Simulated,
|
||
};
|
||
enum xs_plc_transport{
|
||
TCP = 0,
|
||
UDP,
|
||
Serial,
|
||
Raw_Socket,
|
||
PCAP_Replay,
|
||
};
|
||
```
|
||
|
||
:::tip
|
||
注意两者间有对应关系而不是随意组合,如S7(STEP 7)只能采用TCP协议,而Modbus支持tcp/serial/raw socket/pcap replay,可以定义一个函数检查类型:
|
||
xs_PlcProtocolCheck(struct xs_PlcDevice*);
|
||
:::
|
||
|
||
最后,系统中所有注册过的 PLC 设备被组织成一个双链表,即link成员。
|
||
|
||
## 2.XiUOS PLC 控制框架驱动开发
|
||
|
||
以HSC高速计数器为例。控制框架针对每个具体的控制类型将xs_PlcAbility进行扩充,采用类似面向对象的手段添加其他必要成员,如:
|
||
|
||
```c
|
||
struct xs_PlcAbilityHsc {
|
||
struct xs_PlcAbility parent;/* inherit from xs_PlcAbility*/
|
||
uint32_t (*write)(struct xs_PlcAbilityHsc *abl, void* param);
|
||
void (*read)(struct xs_PlcAbilityHsc *abl, void* param)
|
||
};
|
||
```
|
||
|
||
实现xs_PlcOps中的数据通信API,具体实现细节取决于 PLC 型号,无法实现的API可以置为NULL:
|
||
|
||
```c
|
||
structxs_PlcOpshsc_example_ops = {
|
||
.open = hsc_example_open;
|
||
.close = hsc_example_close;
|
||
.read = hsc_example_read;
|
||
.write = hsc_example_write;
|
||
.ioctl = hsc_example_ioctl;
|
||
};
|
||
```
|
||
|
||
实现xs_PlcAbilityHsc中的write和read接口,该接口用于向 PLC 的HSC模块发送控制参数和读取返回参数。在实现过程中可以使用xs_PlcOps中的接口与 PLC 进行通信。
|
||
|
||
其中param为一个void类型指针,由于要写入的命令参数和要读取的返回参数往往不止一个,可以根据不同的控制类型定义不同的数据读写结构体,最后使用结构体指针进行强制类型转换即可。例如对于HSC:
|
||
定义数据写入结构体:
|
||
|
||
```c
|
||
struct hsc_write{
|
||
char id[id_max]; /* id_max is to identify the max HSC ctrl instruction of HSC */
|
||
int cmd; /* HSC control type */
|
||
};
|
||
```
|
||
|
||
其中id用来标识HSC,cmd表示要发送的具体指令,具体的指令可以用一个枚举类型来表示:
|
||
|
||
```c
|
||
enum HscCmdSubType{
|
||
EnHsc = 0, /* enable HSC */
|
||
EnCapture, /* enable the function of capturing the inputs */
|
||
EnSync, /* enable the function of synchronous inputs */
|
||
EnHSCRESET,
|
||
En_CTRL_UPTODOWN ,
|
||
|
||
... ...
|
||
};
|
||
```
|
||
|
||
同理,其他的类型
|
||
|
||
```c
|
||
enum PIDcmdSubType{
|
||
ENHsc = 0,
|
||
EN_XS_PLC_CONTROL_TYPE_PID_COMPACT,
|
||
XS_PLC_CONTROL_TYPE_PID_3STEP , /* valve of motor-driven */
|
||
... ...
|
||
};
|
||
enum PHASEcmdSubType{
|
||
ENPhase = 0,
|
||
XS_PLC_CONTROL_TYPE_PHASING_MC_HALT,
|
||
XS_PLC_CONTROL_TYPE_PHASING_MC_HALT_MOVE_ABSOLUTE,
|
||
... ...
|
||
}
|
||
```
|
||
|
||
定义数据输出结构体:
|
||
|
||
```c
|
||
struct hsc_read{
|
||
uint32_t value; /* the main value you want to get, depends on the cmd */
|
||
bool done; /* indicate whether the SFB has been done */
|
||
bool busy; /* indicate whether the function is busy */
|
||
bool error; /* indicate whether there is error */
|
||
};
|
||
```
|
||
|
||
value是指定返回的变量值,与结构体hsc_write发送的指令cmd类型有关,down/busy/error是一些状态指示位。
|
||
如我们要使用write函数向 PLC 发送HSC类型的控制指令:
|
||
|
||
```c
|
||
struct hsc_write write_example;
|
||
```
|
||
|
||
struct xs_PlcAbilityHsc hsc_example进行必要的初始化后,用强制类型转换作为参数传递给write函数:
|
||
```c
|
||
hsc_example ->write(&hsc_example,(struct hsc_write*)&write_example);
|
||
```
|
||
最后,将 PLC 设备添加到 PLC 框架。分别填充xs_PlcDevice与对应物理量的xs_PlcAbility结构(高速计数器即为xs_PlcAbilityHsc),并依次使用xs_PlcDeviceRegister和xs_PlcAbilityRegister函数将其注册到 PLC 框架:
|
||
|
||
```c
|
||
int xs_PlcDeviceRegister (struct xs_PlcDeviceRegister *pdev);
|
||
int xs_PlcAbilityRegister (struct xs_PlcAbilityRegister *abl);
|
||
|
||
extern struct xs_PlcOps plc_example_ops;
|
||
extern uint32_t plc_example_write(struct xs_PlcAbilityHsc *abl, void *param);
|
||
extern void plc_example_read(struct xs_PlcAbilityHsc *abl, void *param);
|
||
|
||
/* declare xs_PlcDevice and xs_PlcAbilityHsc objects */
|
||
struct xs_PlcDevice hsc_example_pdev;
|
||
struct xs_PlcAbilityHsc hsc_example_abl;
|
||
|
||
void register_example_plc()
|
||
{
|
||
/* initialize and register the xs_PlcDevice object */
|
||
memset(&hsc_example_pdev, 0, sizeof(xs_PlcDevice));
|
||
|
||
hsc_example_pdev.name = "plc1";
|
||
hsc_example_pdev.info.ability |= XS_PLC_ABILITY_HSC;
|
||
hsc_example_pdev.info.vendor = "Siemens";
|
||
hsc_example_pdev.info.product_model = "yyy";
|
||
hsc_example_pdev.ops = &hsc_example_ops;
|
||
hsc_example_pdev.interface.xs_plc_protocol = S7;
|
||
hsc_example_pdev.interface.xs_plc_transport = TCP;
|
||
|
||
xs_PlcDeviceRegister(&hsc_example_pdev);
|
||
|
||
/* initialize and register the xs_PlcAbility of hsc object */
|
||
memset(&hsc_example_abl, 0, sizeof(xs_PlcAbilityHsc));
|
||
|
||
hsc_example_abl.parent.name = "hsc_1";
|
||
hsc_example_abl.parent.type = XS_PLC_TYPE_HSC;
|
||
hsc_example_abl.parent.pdev = &hsc_example_pdev;
|
||
hsc_example_abl.read = hsc_example_read;
|
||
hsc_example_abl.write = hsc_example_write;
|
||
|
||
xs_PlcAbilityRegister((struct xs_PlcAbility *)&hsc_example_abl);
|
||
/* initialize and register otherxs_PlcAbilityobject */
|
||
memset(&other_example_abl, 0, sizeof(xs_PlcAbilityOther));
|
||
... ...
|
||
}
|
||
```
|
||
|
||
## 3. XiUOS PLC 控制框架的使用示例
|
||
PLC 控制应用开发者使用 PLC 控制框架提供的API操作 PLC,PLC 的API可以分为通用API与控制类型特有API。通用API用于 PLC 的获取、打开与关闭,控制类型特有API用于不同种类 PLC 的不同控制指令。以具有HSC高速计时器功能的 PLC 为例:
|
||
|
||
```c
|
||
/* generic API: find a plcability instance by its name */
|
||
struct xs_PlcAbility *xs_PlcAbilityFind(const char *name);
|
||
|
||
/* generic API: open/close a plc ability instance */
|
||
int xs_PlcAbilityOpen(struct xs_PlcAbility *abl);
|
||
void xs_PlcAbilityClose(struct xs_PlcAbility *abl);
|
||
|
||
/* HSC API: send control parameter to PLC and read HSC condition*/
|
||
uint32_t xs_PlcHscWrite(struct xs_PlcAbilityHsc *abl, void *param);
|
||
void xs_PlcHscRead(struct xs_PlcAbilityHsc *abl, void *param);
|
||
```
|
||
|
||
在发送命令和获取数据前,需要先获取并打开要使用的 PLC;PLC 打开后可以随时对 PLC 发送指令和对其数据进行读取;使用完毕后,须关闭 PLC。完整的使用过程示例如下:
|
||
|
||
```c
|
||
int main(int argc, char *argv[])
|
||
{
|
||
int ret;
|
||
structxs_PlcAbility *abl;
|
||
structxs_PlcAbilityHsc *hsc_abl;
|
||
|
||
/* get the Plc hsc ability instance */
|
||
abl = xs_PlcAbilityFind("hsc_1");
|
||
XS_ASSERT(abl->type == XS_PLC_CONTROL_TYPE_HSC);
|
||
|
||
/* open the Plc hscability instance */
|
||
hsc_abl = (struct xs_PlcAbilityHsc*)abl;
|
||
ret = xs_PlcAbilityOpen(abl);
|
||
XS_ASSERT(ret == XS_EOK);
|
||
|
||
/* initialize the write and read data structure */
|
||
structhsc_write write1={
|
||
.id = "xxx";
|
||
.cmd = EnHsc;
|
||
};
|
||
struct hsc_read read1;
|
||
/* send control param to the HSC, just for demonstration */
|
||
xs_PlcHscWrite(hsc_abl,write1);
|
||
/* read data from hsc, just for demonstration */
|
||
xs_PlcHscRead(hsc_abl,read1);
|
||
if(!read1.error)
|
||
xs_kprintf("Read data from PLC HSC is \n", read1.value);
|
||
else
|
||
xs_kprintf("Read data from PLC HSC wrong!\n")
|
||
xs_PlcAbilityClose(abl);
|
||
|
||
return 0;
|
||
}
|
||
```
|
||
|