1、概述
一台典型的工控设备通常包括若干通讯接口(网络、串口、CAN等),以及若干数字IO、AD通道等。运行于设备核心平台的应用程序通过操作这些接口,实现特定的功能。通常为了高效高精度完成整个通讯控制流程,应用程序采用C/C++语言来编写。图1表现了典型工控设备的组成关系。
典型工控设备框图
工控设备的另一个特点是鉴于设备大多是24小时连续运行,且无人值守,所以基本的工控设备是无显示的。英创的工控主板ESM6800、ESM335x等都大量的应用于这类无头工控设备之中。
在实际应用中,部分客户需要基于已有的无头工控设备,增加显示界面功能,以满足新的应用需求。显然保持已有的基本工控处理程序不变,通过相对独立的技术手段来实现显示功能,最符合客户的利益诉求。为此我们发展了一种双进程的程序设计方案来满足客户的这一需求。该方案的第一个进程,以客户已有的用C/C++写的基础工控进程为基础,仅增加一个面向本地IP(127.0.0.1)的侦听线程,用于向显示进程提供必要的运行工况数据。图2为增添了服务线程的工控进程:
带有侦听线程的基础工控进程
方案的第二个进程则主要用于实现显示界面,可以采用各种手段来实现,本文中介绍了使用Qt的QML语言加通讯插件的界面设计方法。第二个进程(具体是通讯插件单元)通过本地IP,以客户端方式与基础工控进程进行Socket通讯,完成进程间数据交换。显示进程以及与工控进程的关系如图3所示:
显示进程与工控进程
2、系统设计
鉴于工业控制领域对系统运行的稳定性要求,控制系统更加倾向于将底层硬件控制部分与上层界面显示分开,两部分以双进程的形式各自独立运行。底层硬件控制部分将会监控系统硬件,管理外设等,同时收集系统的状态;而上层界面显示部分主要用于显示系统状态,并实现少量的系统控制功能,方便维护人员查看系统运行状态并且根据当前状态进行系统的调整。由于显示界面不一定是所有设备都配置,而且显示部分的程序更加复杂,从而更容易出现程序运行时的错误,将控制与显示分开能够避免由于显示部分的程序问题而影响到整个控制系统的运行,而且没有配置显示屏的设备也可以直接运行底层的控制程序,增加了系统程序的兼容性。显示与控制分离后,由于显示界面程序不需要处理底层硬件的管理控制,在设计时可以更加注重于界面的美化,而且界面程序可以采用不同的编程语言进行开发,比如使用Qt C++或者Android java,本文将介绍基于Linux + Qt的双进程示例程序供客户在实际开发中参考,关于Android程序请参考我们官网的另一篇文章:《Android双应用进程Demo程序设计》。
如上图所示。整个系统分为控制和显示两个进程,底层硬件控制部分可以独立运行,使用多线程管理不同的硬件设备,监控硬件状态,将状态发送给socket服务器,并且从socket服务器接收命令来更改设备状态。Socket服务器也是一个独立的线程,通过本地网络通信集中处理来自硬件控制线程以及显示程序的消息。显示界面需要连接上socket服务器才能正确的显示设备的状态,同时提供必须的人工控制接口,供设备使用过程中人为调整设备运行状态。目前在ESM6802工控主板上,界面程序可以采用Qt C++编写,也可以使用Android java进行开发,本文仅介绍采用Qt的界面程序。显示程序界面用QML搭建,与底层通信的部分用独立的Qt QML插件实现,这样显示部分进一部分离为数据处理和界面开发,使得界面设计可以更加快捷。程序的整体界面效果如下图所示:
目前我们只提供了串口(SERIAL)和GPIO两部分的例程。下面将集中介绍程序中通过本地IP实现两个进程通信的部分供客户在实际开发中参考。
3、控制端C程序
控制端程序主要分为两个部分,一个部分用于控制具体的硬件运行(下文称为控制器),另一个部分为socket服务器,用于与显示程序之间进行通信。由于本方案主要是为了展示在已有控制程序的基础上,增加显示界面功能,以满足新的应用需求,所以我们在此重点介绍在已有控制程序中加入socket服务器的部分,不再详细介绍各硬件的具体控制的实现。
增加本地IP通信的功能,首先需要在控制进程中新加入一个socket服务器线程,用于消息的集中管理,实现底层硬件与上层的界面程序的信息交换,socket服务器线程运行的函数体代码如下:
static void *_init_server(void *param) { int server_sockfd, client_sockfd; int server_len; struct sockaddr_in server_address; struct sockaddr_in client_address;
server_sockfd = socket(AF_INET, SOCK_STREAM, 0); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = inet_addr("127.0.0.1");//通过本地ip通信 server_address.sin_port = htons(9733); server_len = sizeof(server_address); bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
listen(server_sockfd, 5);
int res; pthread_t client_thread; pthread_attr_t attr; char id[4]; client_element *client_t;
while(1) { if(!client_has_space(clients)) { printf("to many client, wait for one to quit...\n"); sleep(2); continue; } printf("server waiting\n"); |