OPC-UA远程操控S7-1200
早些年,在济钢焦炉四大机车自动化项目中,我们就通过OPC技术实现了远程数据交互。借助RSLink服务器软件,我们完成了基于OPC DA规范对Rockwell Logix 5500系列PLC的间接控制。这也是OPC技术在工业场景中的典型早期应用。
回顾OPC的发展历史,罗克韦尔自动化、西门子、霍尼韦尔等工业巨头于1995年联合成立了OPC基金会,同年完成了第一个OPC数据访问规范(OPC DA)草案。该规范基于微软COM/DCOM技术构建,为工业数据传输建立了统一标准。 2001 年,规范系列进一步扩展,包括历史数据访问(HDA)、警报和事件(AE) 以及安全规范。 OPC技术迅速流行起来,并成为工业自动化领域事实上的标准。会员公司数量超过100家。然而,以OPC DA为核心的OPC Classic存在明显的局限性。由于它依赖于Windows COM/DCOM技术,无法适配Linux或嵌入式系统,限制了其应用场景的扩展。
为了突破这一技术瓶颈,OPC UA工作组于2003年11月在德国开始开发工作,核心目标是摆脱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%以上工业自动化厂商提供支持,真正实现跨平台、跨设备、跨行业的无缝数据互联。
瑞清牌是瑞赛德推出的产业开发板。它采用Rockchip 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 Portal软件开发。自2009年推出以来,基本上每两年发布一个新版本。最新版本是V21。考虑到安装包的大小和常用功能,我选择了2022年推出的V18版本进行安装。
安装成功后,在配置OPC Server时,发现一旦开启OPC Server功能,部署就会出现异常。以下网页有相关说明。
https://support.industry.siemens.com/cs/document/109971630/tia-portal-crash-when-compiling-the-plc?dti=0lc=en-WW
如果是V18版本,请安装以下补丁解决相关问题。
https://pan.baidu.com/s/1NIjENUkc2GurhxkbKQQJDg 提取码: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/#%E8%BF%90%E8%A1%8C-opc-ua-%E7%A4%BA%E4%BE%8B
OPC UA 客户端:
https://www.unified-automation.com/downloads/opc-ua-clients.html
我们PLC的OPC UA Server IP是192.168.1.200,端口是4840,如上图添加。
连接成功后,将右侧的server interface_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工具来处理它们。图片的大小需要适应液晶显示器(480800),所以我们将图片设置为450480。并保存为png格式。
打开LVGL官方图片在线工具:
https://lvgl.io/工具/imageconverter
导入镜像,会生成对应的C代码文件。
同样,我们也需要显示一些汉字内容,也需要使用官方的文本在线工具生成C代码文件。 LVGL官方文本在线生成工具:
https://lvgl.io/工具/字体转换器
上述功能的主要实现代码如下:
//默认启用字体14、18、22 font_large=lv_font_montserrat_22;字体_正常=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(标题_标签,lv_color_hex(COLOR_DIALOG_TEXT), LV_PART_MAIN); lv_obj_set_style_text_font(标题_标签, YF32HZ, LV_PART_MAIN); lv_label_set_text_fmt(title_label,'%s','热青派OPC-UA对接S7-1200演示'); lv_obj_set_pos(标题标签,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);
四个按钮的代码如下:
voidlv_create_do_button(void){ lv_color_tbtn_bg_color=lv_color_hex(COLOR_BTN_BG); lv_color_tbtn_press_color=lv_color_hex(COLOR_BTN_PRESS); for(uint8_ti=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_tcurr_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个指标的代码如下:
voidlv_draw_state_led(void){ //=====================全局固定参数定义=====================constlv_coord_trect_w_h=8; //矩形大小:8px 宽,8px 高constlv_coord_trect_space=7; //矩形之间的物理间距:7 像素(核心要求) //=====================PLC 状态=====================//起始坐标:x=15+37 y=160+178 |数量: 3 |间距7px | 8*8 |橙色lv_coord_tplc_state_x_start=15+37; lv_coord_tplc_state_y_start=160+178; for(uint8_ti=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_tcurr_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_tplc_di_x_start=15+298; lv_coord_tplc_di_y_start=160+177; for(uint8_ti=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_tcurr_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_tplc_do_x_start=15+298; lv_coord_tplc_do_y_start=160+299; for(uint8_ti=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_tcurr_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连接
intopen62541_connect(char*ip,intport){ charip_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) 读取变量
目前我们只读取了两种类型的变量,就是布尔型和整型,代码如下:
intopen62541_get_value(intns,inti,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 elseif(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); returnretval;}
(3)写变量
我们目前是操作继电器Q变量,该变量是布尔型,所以代码仅支持该类型的写操作。
intopen62541_set_value(intns,inti,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变量的实时读取。
static void opc_thread_entry(void *parameter){ intstate[10]; interr_count =0; intidx[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(inti=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]); inttemp_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);staticvoiddata_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(inti=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
————————————————
版权声明:本文为RT-Thread论坛用户「刘洪峰AIoT」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://club.rt-thread.org/ask/article/d81bd8cab7c57c10.html