【睿擎派】OPC-UA远程操控S7-1200

早年间在济南钢厂焦炉四大机车自动化项目中,我们已通过 OPC 技术实现远程数据交互,借助 RSLink 服务器软件,基于 OPC DA 规范完成对罗克韦尔 Logix 5500 系列 PLC 的间接控制,这也是 OPC 技术在工业场景的典型早期应用。

回溯OPC的发展历程,1995 年Rockwell Automation、Siemens、Honeywell 等工业巨头联合成立 OPC 基金会,同年完成首个 OPC 数据访问规范(OPC DA)草案,该规范基于微软 COM/DCOM 技术构建,为工业数据传输确立了统一标准。2001 年,规范家族进一步扩展,新增历史数据访问(HDA)、报警与事件(AE)及安全规范,OPC 技术迅速普及,成为工业自动化领域的事实标准,会员企业规模突破百家。不过,以 OPC DA 为核心的 OPC Classic 存在明显局限,因依赖 Windows COM/DCOM 技术,无法适配 Linux 或嵌入式系统,限制了其应用场景的拓展。

为突破这一技术瓶颈,2003 年 11 月,OPC UA 工作组在德国启动开发工作,核心目标是摆脱 COM/DCOM 依赖,采用面向服务架构(SOA)重塑技术体系。同年 8 月,OPC UA 1.0 规范正式发布,标志着 OPC 技术进入跨平台时代。2008 年,OPC UA 被国际电工委员会(IEC)采纳为 IEC 62541 标准,完成国际标准化认证;2010 年,首个嵌入式 OPC UA 设备问世,实现了无操作系统 "裸奔" 运行,彻底打破了传统 OPC 的部署限制。2017 年 11 月,OPC UA 1.04 版本发布,引入发布 - 订阅(PubSub)机制,大幅提升了数据传输效率与实时性,更好地满足了大规模工业数据交互需求。

2022 年,OPC UA 技术进一步深化,新增现场交换(UAFX)功能,专为控制器间(C2C)通信设计,支持离线配置与高实时性传输,完美适配工业机器人、CNC 等高精度控制场景。如今,OPC UA 已成为工业 4.0 与工业物联网(IIoT)的核心通信标准,全球 90% 以上的工业自动化厂商均提供支持,真正实现了跨平台、跨设备、跨行业的无缝数据互联。

睿擎派是睿赛德公司推出的一款工业开发板,以瑞芯微 RK3506/RK3563 为主控芯片,底层搭载 RT-Thread 操作系统,基于专为工业场景打造的睿擎工业平台进行开发,该平台是全栈自主可控的软硬件一体化解决方案,整合了数据采集、通信、控制、工业协议、AI、显示六大核心功能,精准适配工业应用需求。所以常见的工业总线通信协议,比如CAN Open、EtherCAT和OPC UA都是支持的。

另外国内工控领域,相对于AB的PLC,西门子的PLC反而应用更为广泛,我们早期就采用S7-200/300/400系列进行工业自动化系统开发。当下西门子主推S7-200Smart,S7-1200和S7-1500。其中S7-1200 固件V4.4版本以上开始支持OPC Server UA。

由此为了深度评测睿擎派的OPC UA能力,我们选用了S7-1200与之对接,实现远程操控PLC的目的。

一、S7-1200 开发环境搭建、编码和OPC 配置

S7-1200是德国西门子公司的PLC产品,采用TIA博途软件进行开发。从2009年推出以来,基本上每一两年就推出一个新版本,当前最新版本是V21,考虑到安装包大小和常用功能,我选择的是2022年推出的V18版本进行安装。

安装成功以后,在配置OPC Server的时候发现,一旦开启OPC Server功能,部署的时候就会异常,如下网页有相关的描述。

https://support.industry.siemens.com/cs/document/109971630/tia-portal-crash-when-compiling-the-plc?dti=0&lc=en-WW

如果是V18版本,安装如下补丁,就可以解决相关问题。

https://pan.baidu.com/s/1NIjENUkc2GurhxkbKQQJDg&nbsp 提取码:pfy6

为了便于测试,我们用梯形图在PLC中编写如下功能:

(1)开关量输入I0.0和继电器Q0.0联动(为了便于中间可控制,通过上升沿和下降沿信号置位和复位的方式来操控继电器)

(2)开启一个定时器,以5秒为间隔,打开和关闭继电器Q0.1

(3)实现计数器功能,便于OPC客户端连续显示一个不断变化的数字

程序编写完毕后,在PLC变量表里会显示相关的变量,当然我们也可以自行增加PLC变量,便于和OPC客户端交互。

这些工作完成后,我们就可以开始进行OPC UA相关的变量配置了。

右侧窗口中的OPC UA元素可以拖动进入到中间的OPC UA服务器接口窗口,会自动添加一条变量。这个顺序很重要,是后续OPC UA客户端读取数据的一个根据(不过如果中间有删除,OPC中的索引顺序并不连续)。

配置完毕后,连带程序代码就可以一起部署到PLC了。

(注意:S7-1200 V4.4以上才支持OPC Server UA。 我当下S7-1200的版本是V4.2.3,所以不支持OPC Server。需要在西门子官方网站下载最新的固件,然后升级PLC即可。当前最新的版本为V4.6.1)

部署成功后,转至在线状态,然后让PLC进入运行状态(RUN)。

二、OPC UA 客户端软件UaExpert 对接测试

由于OPC UA客户端是通过命名空间索引和变量索引获取数据的,所以我们先用OPC UA客户端工具定位相关变量的索引,顺便也测试一下OPC Server是否可以正常工作。

RTT官方示例中有相关工具的下载和使用说明。

https://www.rt-thread.com/ruiching/document/site/rc3506/qy1k2kok/#运行-opc-ua-示例

OPC UA客户端:https://www.unified-automation.com/downloads/opc-ua-clients.html

我们PLC的OPC UA Server IP是192.168.1.200,端口是 4840,按上图添加。

连接成功后,把右侧的服务器接口_1(这个名字,其实是PLC中定义的)标签可以拖动到中间的窗口,则所有的变量会呈现出来。其中如下红框里面的部分,对后续的数据读取格外重要。

三、睿擎派测试代码开发

我们有睿擎派RK3506的开发板和一个4.3英寸的MIPI-DSV的LCD显示触摸屏,所以采lvgl和opc ua技术栈来实现对S7-1200远程操控的功能。

所参考的官方示例分别为:

(1)03_network_opc_ua

示例有opc ua客户端和服务端功能,我们只参考客户端功能即可。

(2)05_gui_lvgl_ethercat_motor_control_7in_1024_600

虽然是7寸屏的功能示例,但是动态显示电机状态的代码我们可以参考。

(3)05_gui_lvgl_mipi_ruiching_4_3in_480_800。

LVGL图形界面完整的示例,可以参考各种控件的功能实现。

有了以上的代码参考,我们要实现的功能其实也蛮简单,就是显示一个S7-1200的PLC图片,IO状态灯和真实的PLC同步显示出来。然后增加四个Q继电器按钮,可以远程开关PLC的四个继电器。另外就是有一个标签,实时显示PLC里面的计数值。

(1) LVGL 界面实现

睿擎派RK3506 V1.7.2SDK集成的LVGL为V9.1.0版本,为2024年3月20日发布的。当前最新版本为V9.4.0于2025年10月16日发布。主要差异就是最新版本支持3D模型和GPU扩展支持。

关于图片,网上可以搜索S7-1200的正面图片,我实际搜索了一下,正面图很少(和我当前型号契合的),并且分辨率不高和模糊,下载后,只好用PS工具加工了一下。图片的尺寸需要和LCD显示器适配(480*800),所以图片我们设定为450*480。并保存为png格式。

打开LVGL官方图片在线工具:https://lvgl.io/tools/imageconverter

把图片导入,会生成对应的C代码文件。

同样我们还需要显示一些汉字内容,也需要用官方文字在线工具进行C代码文件生成

LVGL官方文字在线生成工具:https://lvgl.io/tools/fontconverter

以上的功能的主要实现代码如下:

//默认开启字体 14、18、22

font_large = &lv_font_montserrat_22;

font_normal = &lv_font_montserrat_14;

lv_obj_set_style_text_font(lv_screen_active(), font_normal, 0);

lv_obj_t *title_label = lv_label_create(lv_screen_active());

lv_obj_set_style_text_color(title_label, lv_color_hex(COLOR_DIALOG_TEXT), LV_PART_MAIN);

lv_obj_set_style_text_font(title_label, &YF32HZ, LV_PART_MAIN);

lv_label_set_text_fmt(title_label,"%s","睿擎派OPC-UA对接S7-1200演示");

lv_obj_set_pos(title_label, 20, 30);

num_label = lv_label_create(lv_screen_active());

lv_obj_set_style_text_color(num_label, lv_color_hex(COLOR_BTN_PRESS), LV_PART_MAIN);

lv_obj_set_style_text_font(num_label, font_large, LV_PART_MAIN);

lv_label_set_text_fmt(num_label,"NUM: %06d",0);

lv_obj_set_pos(num_label, 160, 100);

lv_obj_t *plc_img = lv_img_create(lv_screen_active());

lv_img_set_src(plc_img, &S71200W );

lv_obj_set_pos(plc_img, 15, 160);

四个按钮实现的代码如下:

void lv_create_do_button (void)

{

lv_color_t btn_bg_color = lv_color_hex(COLOR_BTN_BG);

lv_color_t btn_press_color = lv_color_hex(COLOR_BTN_PRESS);

for(uint8_t i = 0; i < BTN_CNT; i++)

{

lv_obj_t *btn = lv_btn_create(lv_screen_active());

lv_obj_set_size(btn, BTN_WIDTH, BTN_HEIGHT);

lv_coord_t curr_btn_x = BTN_START_X + i * (BTN_WIDTH + BTN_GAP);

lv_obj_set_pos(btn, curr_btn_x, BTN_START_Y);

lv_obj_set_style_bg_color(btn, btn_bg_color, LV_PART_MAIN); // 默认背景色

lv_obj_set_style_bg_color(btn, btn_press_color, LV_STATE_PRESSED); // 按下背景色

lv_obj_set_style_radius(btn, 4, LV_PART_MAIN); // 圆角

lv_obj_set_style_pad_all(btn, 0, LV_PART_MAIN); // 无内边距

lv_obj_set_style_border_width(btn, 0, LV_PART_MAIN); // 无边框

lv_obj_set_style_bg_opa(btn, LV_OPA_COVER , LV_PART_MAIN); // 不透明

btn_label[i] = lv_label_create(btn);

lv_label_set_text(btn_label[i], btn_text_arr[i]);

lv_obj_set_style_text_color(btn_label[i], lv_color_hex(COLOR_BTN_TEXT), LV_PART_MAIN);

lv_obj_set_style_text_font(btn_label[i], font_large, LV_PART_MAIN); // 正确字体调用

lv_obj_center(btn_label[i]); // 文字水平+垂直绝对居中

lv_obj_add_event_cb(btn, btn_click_event_cb, LV_EVENT_CLICKED, NULL);

}

}

19个指示灯实现的代码如下:

void lv_draw_state_led (void)

{

// ===================== 全局固定参数定义 =====================

const lv_coord_t rect_w_h = 8; // 矩形尺寸:宽8px、高8px

const lv_coord_t rect_space = 7; // 矩形之间的物理间隔:7像素(核心要求)

// ===================== PLC 状态 =====================

// 起始坐标:x=15+37 y=160+178 | 数量:3个 | 间隔7px | 8*8 | 橙色

lv_coord_t plc_state_x_start = 15 + 37;

lv_coord_t plc_state_y_start = 160 + 178;

for(uint8_t i = 0; i < 3; i++)

{

// 1. 创建矩形对象(LVGL9.1 屏幕对象API:lv_screen_active())

plc_state[i] = lv_obj_create(lv_screen_active());

// 2. 设置矩形尺寸 8*8(固定)

lv_obj_set_size(plc_state[i], rect_w_h, rect_w_h);

// 3. 计算X坐标(核心:保证间距7px)、Y坐标固定

lv_coord_t curr_x = plc_state_x_start + i * (rect_w_h + rect_space);

lv_obj_set_pos(plc_state[i], curr_x, plc_state_y_start);

// 4. 样式配置:纯色填充、直角、无边框(小矩形标准样式)

lv_obj_set_style_bg_color(plc_state[i], lv_color_hex(COLOR_BLACK), LV_PART_MAIN);

lv_obj_set_style_radius(plc_state[i], 0, LV_PART_MAIN); // 圆角0 → 纯矩形

lv_obj_set_style_border_width(plc_state[i], 0, LV_PART_MAIN); // 无边框(无多余线条)

lv_obj_set_style_pad_all(plc_state[i], 0, LV_PART_MAIN); // 无内边距

}

// ===================== PLC DI =====================

// 起始坐标:x=15+298 y=160+177 | 数量:6个 | 间隔7px | 8*8 | 绿色

lv_coord_t plc_di_x_start = 15 + 298;

lv_coord_t plc_di_y_start = 160 + 177;

for(uint8_t i = 0; i < 8; i++)

{

plc_di[i] = lv_obj_create(lv_screen_active());

lv_obj_set_size(plc_di[i], rect_w_h, rect_w_h);

lv_coord_t curr_x = plc_di_x_start + i * (rect_w_h + rect_space);

lv_obj_set_pos(plc_di[i], curr_x, plc_di_y_start);

lv_obj_set_style_bg_color(plc_di[i], lv_color_hex(COLOR_BLACK), LV_PART_MAIN);

lv_obj_set_style_radius(plc_di[i], 0, LV_PART_MAIN);

lv_obj_set_style_border_width(plc_di[i], 0, LV_PART_MAIN);

lv_obj_set_style_pad_all(plc_di[i], 0, LV_PART_MAIN);

}

// ===================== PLC DQ =====================

// 起始坐标:x=15+298 y=160+299 | 数量:4个 | 间隔7px | 8*8 | 绿色

lv_coord_t plc_do_x_start = 15 + 298;

lv_coord_t plc_do_y_start = 160 + 299;

for(uint8_t i = 0; i < 8; i++)

{

plc_do[i] = lv_obj_create(lv_screen_active());

lv_obj_set_size(plc_do[i], rect_w_h, rect_w_h);

lv_coord_t curr_x = plc_do_x_start + i * (rect_w_h + rect_space);

lv_obj_set_pos(plc_do[i], curr_x, plc_do_y_start);

lv_obj_set_style_bg_color(plc_do[i], lv_color_hex(COLOR_BLACK), LV_PART_MAIN);

lv_obj_set_style_radius(plc_do[i], 0, LV_PART_MAIN);

lv_obj_set_style_border_width(plc_do[i], 0, LV_PART_MAIN);

lv_obj_set_style_pad_all(plc_do[i], 0, LV_PART_MAIN);

}

}

初始的时候,LED灯都显示黑色。OPC Server连接成功后,PLC RUN灯位绿色,否则为橙色。开关量输入和输出灯根据实际状态进行变化。

(2) OPC UA 客户端功能实现

睿擎派RK3506 V1.7.2 SDK集成的open62541为V1.2.2版本,2019年9月18日发布的,属于早期经典版,基础功能完善,封装相对简单。最新工业级稳定版本为V1.5.1,2024年11月12日发布。核心区别如下,V1.2.2没有批量读取变量的函数,无PubSub(发布-订阅)功能,V1.5.1原生适配RT-Thread,内存优化,但是需要C99编译支持。

OPC UA客户端有三个关键功能函数,我们专门创建open62541_client.c文件来实现,由于我们本代码示例是基于05_gui_lvgl_mipi_ruiching_4_3in_480_800创建的,所以需要双击"RunChing Setings"项,开启OPC UA功能,如下图所示:

注:开启OPC UA功能后,\opc_ua_lvgl_s7_1200\rt-thread\components\net_apps\open62541的目录并没有加入到Includes目录,记得要添加上,否则对应的头文件编译时会提示找不到。

1 opc ua server 连接

int open62541_connect (char *ip,int port)

{

char ip_data[128] = {0};

rt_sprintf(ip_data,"opc.tcp://%s:%d", ip, port);

if(client==NULL) {

client = UA_Client_new();

UA_ClientConfig_setDefault(UA_Client_getConfig(client));

}

UA_StatusCode retval = UA_Client_connect(client, ip_data);

if(retval != UA_STATUSCODE_GOOD)

{

UA_Client_delete(client);

client = NULL;

return (int)retval;

}

return (int)retval;

}

提供ip地址和端口即可,目前没有开启安全验证,所以相对简单。

2 读取变量

目前我们只读取了两种类型的变量,就是布尔型和整型,代码如下:

int open62541_get_value (int ns,int i, int *value)

{

if (client==NULL) return -1;

UA_Variant read_value;

UA_Variant_init(&read_value);

UA_StatusCode retval = UA_Client_readValueAttribute(client,UA_NODEID_NUMERIC(ns,i), &read_value);

if(retval == UA_STATUSCODE_GOOD)

{

UA_UInt32 type_num = -1;

// 提取类型编号

if(read_value.type != NULL) {

type_num = read_value.type->typeIndex;

}

//Boolean

if(type_num==0)

{

UA_Boolean *p = (UA_Boolean *)read_value.data;

*value = (int)*p;

}

//UInt32

else if(type_num==4)

{

UA_UInt32 *p = (UA_UInt32 *)read_value.data;

*value = (int)*p;

}

else {

rt_kprintf("[err]type_num=%d\n", type_num);

}

//rt_kprintf(" - 类型: %s (编号: %u) arrayLength = %d\n", ua_typeid_to_name(type_num), type_num,read_value.arrayLength);

}

else

{

rt_kprintf("get [%d.%d] failed, code: %d\n", ns,i, retval);

}

UA_Variant_clear(&read_value);

return retval;

}

3 写变量

我们目前是操作继电器Q变量,该变量是布尔型,所以代码仅支持该类型的写操作。

int open62541_set_value (int ns,int i,UA_Boolean value)

{

if (client==NULL) return -1;

UA_Variant write_value;

UA_Variant_setScalar(&write_value, &value, &UA_TYPES[UA_TYPES_BOOLEAN]);

UA_StatusCode retval = UA_Client_writeValueAttribute(client, UA_NODEID_NUMERIC(ns,i), &write_value);

if(retval != UA_STATUSCODE_GOOD)

{

rt_kprintf("set [%d.%d] failed retval = %d\n",ns,i,retval);

}

return (int)retval;

}

(3) 变量远程实时读写

opc读写函数中的参数int ns,int i 就对应了OPC客户端工具画红框的部分,比如"NS4|Numeric|8", 相应的ns=4,i=8;

LVGL不支持多线程操作,所以需要创建一个LVGL 定时器来定时刷新数据,另外由于定时器函数属于UI线程回调,如果里面做长时间操作,会堵塞UI线程,界面操作会很卡。所以需要新创建一个线程来实现OPC UA变量的实时读取。

/OPC-UA链接线程

static void opc_thread_entry (void *parameter)

{

int state[10];

int err_count = 0;

int idx[10]={4,5,6,7,8,10,11,12,13,14};

while(1)

{

if(plc_connect_state!=0)

{

if(open62541_connect("192.168.1.200",4840)==0)

{

plc_connect_state = 0;

}

}

else

{

for (int i=0;i<10;i++){

if(open62541_get_value(4,idx[i],&state[i])==0){

io_state[i] = state[i];

err_count = 0;

}

else {

rt_kprintf("open62541_get_value %d err!!\n",i);

if(err_count++>10)

{

plc_connect_state = -1;

}

}

}

rt_kprintf("I %d %d %d %d %d %d\n",io_state[4],io_state[5],io_state[6],io_state[7],io_state[8],io_state[9]);

rt_kprintf("Q %d %d %d %d\n\n",io_state[0],io_state[1],io_state[2],io_state[3]);

int temp_num = 0;

if(open62541_get_value(4,15,&temp_num)==0){

num = temp_num;

}

}

rt_thread_mdelay(100);

}

}

创建一个定时器,300毫秒执行一次,来进行界面刷新。

lv_timer_create(data_timer_cb, 300, NULL);

static void data_timer_cb(lv_timer_t *timer)

{

//PLC状态

if(plc_connect_state!=old_connect_state)

{

lv_obj_set_style_bg_color(plc_state[0], lv_color_hex(plc_connect_state==0?COLOR_GREEN:COLOR_ORANGE), LV_PART_MAIN);

old_connect_state = plc_connect_state;

}

//计数

lv_label_set_text_fmt(num_label,"NUM: %06d",num);

//IO状态

for (int i=0;i<10;i++){

if(i < 4)

{

lv_obj_set_style_bg_color(plc_do[i],lv_color_hex( io_state[i]!=1?COLOR_BLACK:COLOR_GREEN), LV_PART_MAIN);

lv_obj_set_style_text_color(btn_label[i], lv_color_hex(io_state[i]!=1?COLOR_BTN_TEXT:COLOR_GREEN), LV_PART_MAIN);

}

else

{

lv_obj_set_style_bg_color(plc_di[i-4],lv_color_hex( io_state[i]!=1?COLOR_BLACK:COLOR_GREEN), LV_PART_MAIN);

}

}

}

由于写变量操作,执行实现不长,所以直接在按钮回调事件里实现了。

static void btn_click_event_cb(lv_event_t *e)

{

lv_event_code_t code = lv_event_get_code(e);

lv_obj_t *btn = lv_event_get_target(e);

if (code == LV_EVENT_CLICKED)

{

const char *btn_name = lv_label_get_text(lv_obj_get_child(btn, 0));

int index = btn_name[3]-'0';

rt_kprintf("DO: %s %d\n", btn_name,index);

if(open62541_set_value(4,4+index,1-io_state[index])==0)

{

io_state[index] = 1-io_state[index];

}

}

}

四、操作演示视频

(1 )部署运行

部署成功后,程序会自动运行,连接成功后,会不断读取PLC的IO状态及计数器的值。

(2 )操作演示

视频链接:https://www.bilibili.com/video/BV1dQqMBgEKY/?spm_id_from=333.1387.homepage.video_card.click&vd_source=9d246b49a8f4b0a5dce8f3f5eba833cb

相关推荐
华奥系科技12 小时前
智慧经济新格局:解码社区、园区与城市一体化建设逻辑
大数据·人工智能·科技·物联网·安全
TDengine (老段)12 小时前
TDengine IDMP 组态面板 —— 画布
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
蓝奥声科技18 小时前
扩展式智能插座,破解多国标准与定制需求的新思路
物联网·智能用电计量插座·lpiot 低功耗物联网·外贸插座
Zevalin爱灰灰19 小时前
零基础入门学用物联网(ESP8266) 第一部分 基础知识篇(三)
单片机·物联网·嵌入式·esp8266
我爱我家88220 小时前
亚洲艺术电影节携澳门文化亮相深圳
人工智能·物联网·算法·区块链·爬山算法
物联通信量讯说20 小时前
从5G迈向未来通信时代,量讯物联深耕连接基础能力
物联网·5g·信息与通信·iot·通信·6g·量讯物联
搜佛说21 小时前
RocksDB, SQLite, TDengine Edge, LiteDB与sfsDb选型
物联网·edge·sqlite·边缘计算·时序数据库·iot·tdengine
沐欣工作室_lvyiyi21 小时前
基于物联网的体温心率监测系统(论文+源码)
stm32·单片机·嵌入式硬件·物联网·体温心率
QYR_111 天前
香叶醇行业深度解析:香精香料领域核心原料的发展潜力与挑战
大数据·人工智能·物联网
taxunjishu1 天前
塔讯总线协议转换信捷 PLC 对接 TCP/IP 设备实战方案
网络·物联网·自动化