当前位置: 首页 > article >正文

Qt程序基于共享内存读写CodeSys的变量

文章目录

  • 1.背景
  • 2.结构体从CodeSys导出后导入到C++
    • 2.1.将结构体从CodeSys中导出
    • 2.2.将结构体从m4文件提取翻译成c++格式
  • 3.添加RTTR注册信息
  • 4.读取PLC变量值
  • 5.更改PLC变量值

1.背景

在文章【基于RTTR在C++中实现结构体数据的多层级动态读写】中,我们实现了通过字符串读写结构体中的变量。那么接下来我们开始与CodeSys来进行交互。
由于我们是基于共享内存来通讯的,那么我们需要对共享的内存定义一个数据结构,也就是一个结构体。
假如我们和PLC的通讯只是简单的一个结构体,结构体中都是一些POD(Plain Old Data),那可以直接和PLC程序编写人员协商沟通好,让他把结构的定义代码发给你,你再根据ST代码写出结构体的C++代码。
但是,在实际的项目中,要到使用到的结构体往往是多种类型的结构体互相嵌套的结果。不仅结构体多、数据多,而且还存在数组、嵌套的方式,单纯靠手工来拷贝ST代码-》转C++代码必定是繁琐且容易出错的。因此,必须得搞一套稳定可靠的导出导入机制。
这里我选择通过利用CodeSys的机制+python脚本来实现

2.结构体从CodeSys导出后导入到C++

要想将Application的结构体数据直接导出,貌似是不行的,但是可以先把结构体数据复制到一个Library工程,然后导出m4文件,最后利用python脚本翻译(处理)成我们需要的代码。

2.1.将结构体从CodeSys中导出

我这里有一个Application工程,里面定义了若干结构体
在这里插入图片描述

假如想将其导出,那么可以新建一个Library工程,然后将结构体复制过去(直接在左侧的树状列表中选择、复制,而不是直接复制代码)。
在这里插入图片描述
然后选择 编译–》生成运行时系统文件
在这里插入图片描述
然后勾选M4接口文件
在这里插入图片描述
然后点击确定、生成M4文件。
如此,便完成了结构体的导出。

2.2.将结构体从m4文件提取翻译成c++格式

其实打开M4文件看一下,可以发现,导出数据已经是c语言格式的结构体了,基本都可以拿来直接用了,但是由于后面要和RTTR结合使用,必须还得清洗处理一下。
在这里插入图片描述M4文件的清洗处理我们需要用到clang(LLVM)库。
我们是在python中使用clang,因此我们需要在python中安装此工具包,我安装的是20.1.0:
在这里插入图片描述但是在python中安装了还不行,还得去官网将依赖的库及程序文件下载下来
【llvm github】
在这里插入图片描述下载之后,解压到某个路径下即可,不用安装
在这里插入图片描述
然后就可以使用脚本了,这是我的脚本
在脚本中指定好M4文件所在路径、中间文件保存路径、最终文件保存路径,运行即可

import sys
import clang.cindex
from clang.cindex import CursorKind, TypeKind, Config
import os# 前面提到的clang压缩包的解压的路径,根据自己的路径指定
Config.set_library_path("D:/Qt/clang+llvm-20.1.0-x86_64-pc-windows-msvc/bin")# M4文件位置
m4FilePath = r'C:/Users/Administrator/Desktop/stateTest/StructOutputItf.m4'
# 中间文件位置
middleFilePath = "./output/tmpFile.h"
# 处理后的文件位置
outputFilePath = "./output/memorydefine.h"def convert_c_struct_to_cpp(input_file, output_file):index = clang.cindex.Index.create()# tu = index.parse(input_file, args=['-std=c++11'])# Windows特定参数args = ['-finput-charset=UTF-8','-std=c++11','-x', 'c++',  # 强制按C++模式解析# r'-IC:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\include',  # MSVC头文件路径# r'-IC:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt',  # Windows SDK路径,r'-IC:\Program Files\CODESYS 3.5.19.60\CODESYS\CODESYS Control SL Extension Package\4.10.0.0\ExtensionSDK\include',# Windows SDK路径,# r'-ID:\Qt5.15\5.15.2\msvc2019_64\include\QtCore',]tu = index.parse(input_file, args=args)struct_defs = []def analyze_typedef(node):if node.kind == CursorKind.TYPEDEF_DECL:canonical_type = node.type.get_canonical()# print("--", canonical_type.spelling)# if canonical_type.spelling.startswith("tag"):#     print("--", canonical_type.spelling)if canonical_type.kind == TypeKind.RECORD:struct_decl = canonical_type.get_declaration()struct_defs.append({'new_name': node.spelling,'members': list(get_struct_members(struct_decl))})def get_struct_members(struct_decl):for child in struct_decl.get_children():if child.kind == CursorKind.FIELD_DECL:# print("type:", child.type.spelling, child.type.get_array_size(), child.type.get_canonical().spelling)child_type = child.type.spellingchild_name = child.spellingif child.type.get_array_size() != -1:  # 数组需要特殊处理prefix = child_type.split('[')[0]child_type = prefixchild_name += child.type.spelling.replace(prefix, "")# print("----", child_type, child_name)if child_type == 'int' or child_type == 'int *':child_type = '没定义_自己处理'yield {'type': child_type,'name': child_name}def generate_cpp_struct(def_info):lines = [f"struct {def_info['new_name']}","{"]for member in def_info['members']:lines.append(f"    {member['type']} {member['name']};")lines.append("};\n")return '\n'.join(lines)# AST遍历for node in tu.cursor.get_children():analyze_typedef(node)# 生成纯净CPP代码output_content = """#ifndef MEMORYDEFINE_H
#define MEMORYDEFINE_H
#include "CmpStd.h"
// ST语言的数据类型所占用的字节数:https://blog.csdn.net/u013186651/article/details/135324625
// 默认string类型的字节为:80 + 1
// 转换之后,假如出现了 int ,那么这个类型应该就是没有被正确识别,需要手动替换处理// 有很多系统的第三方的库结构是没办法导出,因此需要自己在PLC系统中测量,然后自行用数组类型替换
// 替换的目的是内存对齐
// SMC_POS_REF -->48 Byte
// MC_KIN_REF_SM3 --> 8 Byte
// Kin_ArticulatedRobot_6DOF --> 760 Byte"""output_content += '\r\n'.join([generate_cpp_struct(d) for d in struct_defs])output_content += '\r\n#endif // MEMORYDEFINE_H'# 写入文件os.makedirs(os.path.dirname(output_file), exist_ok=True)with open(output_file, 'w', encoding='utf-8') as f:f.write(output_content)if __name__ == "__main__":source_code = """#include "CmpStd.h"// 系统未定义或者M4文件没有导出的类型,然后又通过PLC程序知道其长度struct SMC_POS_REF{int8_t data[48];};struct MC_KIN_REF_SM3{int8_t data[8];};struct Kin_ArticulatedRobot_6DOF{int8_t data[760];};"""# 读取m4文件内容with open(m4FilePath, 'r', encoding='utf-8') as m4File:content = m4File.read()# 找到开始和结束标记的位置start_marker = """
#ifdef __cplusplus
extern "C" {
#endif
"""end_marker = """
#ifdef __cplusplus
}
#endif
"""start_index = content.find(start_marker) + len(start_marker)end_index = content.find(end_marker)print(start_index, end_index)# 检查是否找到了开始和结束标记if start_index != -1 and end_index != -1:# 截取标记之间的内容extracted_content = content[start_index:end_index]source_code += extracted_content# 创建一个临时文件with open(middleFilePath, 'w', encoding='utf-8') as middleFile:# 将源代码字符串写入文件middleFile.write(source_code)# 确保内容被写入磁盘middleFile.flush()print("开始转换")convert_c_struct_to_cpp(middleFilePath, outputFilePath)print("操作完成-----》")else:print("m4文件内容有误,无法提取")

脚本的一些注意事项已经在代码中注释了,就不另外说明了。
运行完脚本,就可以得到了符合我们需求的c++格式的代码文件了

在这里插入图片描述脚本先将M4文件中的主要内容提取出来,然后添加一个头文件保存为一个中间文件。此时这个中间文件的结构体的定义还是c风格的。
然后将此中间文件交给clang解析,将结构体的内容分析出来,然后再将结构体的名称由原来的带tag的替换成没有带tag的。最后将所有结构体的内容保存成一个cpp风格的h文件。
需要注意的是,生成的头文件中有很多不必要的信息,自己手动删除即可。

3.添加RTTR注册信息

从我们前一篇文章可以知道,要使用RTTR的功能,必须要对每一个结构体进行注册处理。我们结构体这么多,一个个手动写代码,不现实。我们还是用脚本来自动处理吧,这个脚本输入的是前面脚本生成的头文件:

import sys
import clang.cindex
from clang.cindex import CursorKindclang.cindex.Config.set_library_path("D:/Qt/clang+llvm-20.1.0-x86_64-pc-windows-msvc/bin")srcFilePath = "./output/memorydefine.h"
dstFilePath = "./output/memorydefine.cpp"def get_struct_members(cursor):members = []for child in cursor.get_children():if child.kind == CursorKind.FIELD_DECL:member_type = child.type.spelling# 处理数组类型(保留方括号)if child.type.get_array_size() != -1:array_size = child.type.get_array_size()member_type = f"{child.type.element_type.spelling}[{array_size}]"members.append((child.spelling, member_type))return membersdef generate_rttr_code(structs_map):code = "RTTR_REGISTRATION\n{\n"for struct_name, members in structs_map.items():code += f"    registration::class_<{struct_name}>(\"{struct_name}\")\n"for member_name, _ in members:code += f"    .property(\"{member_name}\", &{struct_name}::{member_name})(policy::prop::as_reference_wrapper)\n"code += "    ;\n\n"code += "}"return codedef analyze_header(file_path):index = clang.cindex.Index.create()# Windows特定参数args = ['-std=c++11','-x', 'c++',  # 强制按C++模式解析# r'-IC:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\include',  # MSVC头文件路径# r'-IC:\Program Files (x86)\Windows Kits\10\Include\10.0.19041.0\ucrt',  # Windows SDK路径,r'-IC:\Program Files\CODESYS 3.5.19.60\CODESYS\CODESYS Control SL Extension Package\4.10.0.0\ExtensionSDK\include',r'-ID:\Qt5.15\5.15.2\msvc2019_64\include\QtCore']tu = index.parse(file_path, args=args)structs = {}def visit_node(cursor):if cursor.kind == CursorKind.STRUCT_DECL and cursor.is_definition():struct_name = cursor.spellingif struct_name and not struct_name.startswith('_'):  # 忽略匿名结构体structs[struct_name] = get_struct_members(cursor)for child in cursor.get_children():visit_node(child)visit_node(tu.cursor)return generate_rttr_code(structs)if __name__ == "__main__":# print(analyze_header("MyStruct.h"))# print(analyze_header("E:\zhongyong\zyQt\Robot\CommunicationTest\communication\sharedMemory\memorydefine.h"))fileContent = """#include "memorydefine.h"#include <rttr/registration>
#include <rttr/type>
#include <vector>using namespace rttr; 
"""fileContent += analyze_header(srcFilePath)with open(dstFilePath, 'w', encoding='utf-8') as f:f.write(fileContent)

处理完之后,我们就得到了RTTR注册的代码
在这里插入图片描述这个处理后生成的代码,就保存成cpp文件。只要将前面生成的h文件一起加到我们自己的工程,我们就可以对PLC放在共享内存上的结构体全知全晓了。

4.读取PLC变量值

读取变量直接将结构体指针指向约定好的那一块共享内存,然后读即可。
可以选择直接用变量名读,也可以通过RTTR的字符串属性来读,选择你喜欢的方式就好。

5.更改PLC变量值

这个稍微复杂一些。
首先,在我们已经在【基于RTTR在C++中实现结构体数据的多层级动态读写】中实现了获取某个子成员地址相对于主数据的地址的偏移,而经过测试、CodeSys上的数据结构及结构体的对齐策略是与Qt这边是一致的。
因此,我们完全可以将要写的变量的值+类型+地址的偏移发送给PLC,PLC收到之后,按照偏移来对变量赋值
在这里插入图片描述要实现这个功能,得灵活使用结构体、指针和共用体。
更加具体的代码就不详述了。


参考:
【基于RTTR在C++中实现结构体数据的多层级动态读写】
【共享内存 - C#与CoDeSys通讯】
【clang 在 Windows 下的安装教学】
【CodeSys平台ST语言编程】

相关文章:

Qt程序基于共享内存读写CodeSys的变量

文章目录 1.背景2.结构体从CodeSys导出后导入到C2.1.将结构体从CodeSys中导出2.2.将结构体从m4文件提取翻译成c格式 3.添加RTTR注册信息4.读取PLC变量值5.更改PLC变量值 1.背景 在文章【基于RTTR在C中实现结构体数据的多层级动态读写】中&#xff0c;我们实现了通过字符串读写…...

7-12 关于堆的判断

输入样例&#xff1a; 5 4 46 23 26 24 10 24 is the root 26 and 23 are siblings 46 is the parent of 23 23 is a child of 10输出样例&#xff1a; F T F T 这题是建最小堆&#xff0c;数据结构牛老师讲过这个知识点&#xff0c;但是我给忘了&#xff0c;补题搜了一下才解…...

《SQL编程思想》中的 MySQL 建表语句和测试数据

《SQL编程思想》中的 MySQL 建表语句 建表语句 -- 创建 4 个示例表和索引 CREATE TABLE department( dept_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 部门编号&#xff0c;自增主键, dept_name VARCHAR(50) NOT NULL COMMENT 部门名称) ENGINEInnoDB COMM…...

STL标准库

感谢哔哩哔哩UP“开发者LaoJ”&#xff0c;以下是学习记录~ 一、容器 1.1、vector 底层实现是动态数组&#xff0c;向尾部插入数据很方便&#xff0c;但是向中间和头部插入数据需要移动其它元素 可以实现随机访问 如果插入时&#xff0c;当前vector容纳不下&#xff0c;会…...

STM32 HAL库实战:高效整合DMA与ADC开发指南

STM32 HAL库实战&#xff1a;高效整合DMA与ADC开发指南 一、DMA与ADC基础介绍 1. DMA&#xff1a;解放CPU的“数据搬运工” DMA&#xff08;Direct Memory Access&#xff09; 是STM32中用于在外设与内存之间直接传输数据的硬件模块。其核心优势在于无需CPU干预&#xff0c;…...

什么是机器学习?从零基础到自动驾驶案例全解析

Langchain系列文章目录 01-玩转LangChain&#xff1a;从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块&#xff1a;四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain&#xff1a;从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...

正点原子[第三期]Arm(iMX6U)Linux移植学习笔记-4 uboot目录分析

前言&#xff1a; 本文是根据哔哩哔哩网站上“Arm(iMX6U)Linux系统移植和根文件系统构键篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。 引用&#xff1a; …...

Unity开发——点击事件/射线检测

一、IPointerClickHandler接口 通过为 UI 元素添加自定义脚本&#xff0c;实现IPointerClickHandle接口&#xff0c;在点击事件发生时进行处理。 这种方式适用于对特定 UI 元素的点击检测。 using UnityEngine; using UnityEngine.EventSystems;public class UIClickHandler…...

【零基础入门unity游戏开发——unity3D篇】3D物理系统之 —— 3D刚体组件Rigidbody

考虑到每个人基础可能不一样,且并不是所有人都有同时做2D、3D开发的需求,所以我把 【零基础入门unity游戏开发】 分为成了C#篇、unity通用篇、unity3D篇、unity2D篇。 【C#篇】:主要讲解C#的基础语法,包括变量、数据类型、运算符、流程控制、面向对象等,适合没有编程基础的…...

微信小程序接入DeepSeek模型(火山方舟),并在视图中流式输出

引言&#xff1a; DeepSeek&#xff0c;作为一款先进的自然语言处理模型&#xff0c;以其强大的文本理解和生成能力著称。它能够处理复杂的文本信息&#xff0c;进行深度推理&#xff0c;并快速给出准确的回应。DeepSeek模型支持流式处理&#xff0c;这意味着它可以边计算边输…...

55年免费用!RevoUninstaller Pro专业版限时领取

今天&#xff0c;我要给大家介绍一款超给力的卸载工具——RevoUninstaller Pro。这是一款由保加利亚团队精心打造的专业级卸载软件&#xff0c;堪称软件卸载界的“神器”。 RevoUninstaller分为免费版和专业版。专业版功能更为强大&#xff0c;但通常需要付费才能解锁全部功能。…...

Markdig:强大的 .NET Markdown 解析器详解

在现代开发中&#xff0c;Markdown 已经成为了一种广泛使用的轻量级标记语言&#xff0c;特别是在文档、博客和内容管理系统中&#xff0c;Markdown 为开发者提供了快速、简洁的格式化文本方式。而在 .NET 生态中&#xff0c;Markdig 是一款非常强大的 Markdown 解析器&#xf…...

基于ensp的IP企业网络规划

基于ensp的IP企业网络规划 前言网络拓扑设计功能设计技术详解一、网络设备基础配置二、虚拟局域网&#xff08;VLAN&#xff09;与广播域划分三、冗余协议与链路故障检测四、IP地址自动分配与DHCP相关配置五、动态路由与安全认证六、广域网互联及VPN实现七、网络地址转换&#…...

谷歌Chrome或微软Edge浏览器修改网页任意内容

在谷歌或微软浏览器按F12&#xff0c;打开开发者工具&#xff0c;切换到console选项卡&#xff1a; 在下面的输入行输入下面的命令回车&#xff1a; document.body.contentEditable"true"效果如下&#xff1a;...

初探大模型开发:使用 LangChain 和 DeepSeek 构建简单 Demo

最近&#xff0c;我开始接触大模型开发&#xff0c;并尝试使用 LangChain 和 DeepSeek 构建了一个简单的 Demo。通过这个 Demo&#xff0c;我不仅加深了对大模型的理解&#xff0c;还体验到了 LangChain 和 DeepSeek 的强大功能。下面&#xff0c;我将分享我的开发过程以及一些…...

【Linux】进程(1)进程概念和进程状态

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;Linux 目录 前言 一、什么是进程 二、task_struct的内容 三、Linux下进程基本操作 四、父进程和子进程 1. 用fork函数创建子进程 五、进程状态 1. 三种重…...

关闭win11根据内容自动调整屏幕亮度

在win11笔记本上使用编程软件的时候&#xff0c;用的是深色背景&#xff0c;但是屏幕会慢慢变暗&#xff1b;等切换回明亮的桌面时&#xff0c;又会慢慢变亮&#xff0c;带来不适应的感觉。这个博客记录一下解决这个问题的办法 ps&#xff1a;有些人修改的是电源选项&#xff…...

2021-05-23 C++百元百鸡

此是草稿&#xff0c;有值得优化的地方&#xff0c;如从公鸡先循环再母鸡再小鸡这样可以提高效率&#xff0c;且有输出后也可优化为公鸡母鸡小鸡初始化。 void 百元百鸡() {//缘由https://ask.csdn.net/questions/7434093?spm1005.2025.3001.5141int xj 1, mj 1, gj 1, y …...

理解langchain langgraph 官方文档示例代码中的MemorySaver

以下是langchain v0.3官方示例代码 from langgraph.checkpoint.memory import MemorySaver from langgraph.graph import START, MessagesState, StateGraph# 可以理解为&#xff1a;定义一个流程&#xff0c;这个流程中用到的数据类型是Messages。 <---定义一个有向图&…...

C# 建造者模式(Builder Pattern)详细讲解

一、什么是建造者模式&#xff1f; 建造者模式&#xff08;Builder Pattern&#xff09;是一种创建型设计模式&#xff0c;它通过将一个复杂对象的构建过程与其表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。这个模式主要应用于那些构建过程复杂且涉及多个步骤的…...

Android自动化测试工具

细解自动化测试工具 Airtest-CSDN博客 以下是几种常见的Android应用自动化测试工具&#xff1a; Appium&#xff1a;支持多种编程语言&#xff0c;如Java、Python、Ruby、JavaScript等。可以用于Web应用程序和原生应用程序的自动化测试&#xff0c;并支持iOS和Android平台。E…...

【蓝桥杯】24省赛:数字串个数

思路 本质是组合数学问题&#xff1a; 9个数字组成10000位数字有9**10000可能 不包括3的可能8**10000 不包括7的可能8**10000 既不包括3也不包括77**10000 根据容斥原理&#xff1a;结果为 9 ∗ ∗ 10000 − 8 ∗ ∗ 10000 − 8 ∗ ∗ 10000 7 ∗ ∗ 10000 9**10000 - 8**10…...

SpringBoot中使用kaptcha生成验证码

简介 kaptcha是谷歌开源的简单实用的验证码生成工具。通过设置参数&#xff0c;可以自定义验证码大小、颜色、显示的字符等等。 Maven引入依赖 <!-- https://mvnrepository.com/artifact/pro.fessional/kaptcha --><dependency><groupId>pro.fessional<…...

解密乐天音乐如何通过抗指纹浏览器刷变现

作者&#xff1a;药尘-韩立----->&#x1f680;&#x1f30d;❤️( hanli068 ) 专栏&#xff1a;反反爬技术 日期&#xff1a;2025年3月15日 原创声明&#xff1a;本文为作者原创&#xff0c;未经允许不得转载 在音乐流媒体平台&#xff08;“日本乐天音乐”&#xff09;中…...

自定义tiptap插件

本文为开发开源项目的真实开发经历&#xff0c;感兴趣的可以来给我的项目点个star&#xff0c;谢谢啦~ 具体博文介绍&#xff1a; 开源&#xff5c;Documind协同文档&#xff08;接入deepseek-r1、支持实时聊天&#xff09;Documind &#x1f680; 一个支持实时聊天和接入 - 掘…...

软考网络安全专业

随着信息技术的迅猛发展&#xff0c;网络安全问题日益凸显&#xff0c;成为社会各界普遍关注的焦点。在这样的背景下&#xff0c;软考网络安全专业应运而生&#xff0c;为培养高素质的网络安全人才提供了有力支撑。本文将对软考网络安全专业进行深入剖析&#xff0c;探讨其在信…...

蓝桥杯嵌入式赛道复习笔记1(led点亮)

前言 基础的文件创建&#xff0c;参赛资源代码的导入&#xff0c;我就不说了&#xff0c;直接说CubeMX的配置以及代码逻辑思路的书写&#xff0c;在此我也预祝大家人人拿国奖 理论讲解 原理图简介 1.由于存在PC8引脚到PC15引脚存在冲突&#xff0c;那么官方硬件给的解决方案…...

订单超时自动取消功能如何设计

在一个 Java 电商项目中&#xff0c;订单超时自动取消功能可以通过多种方式设计和实现。常见的实现方式包括使用定时任务&#xff08;例如 ScheduledExecutorService 或 Spring 的 Scheduled 注解&#xff09;或者基于事件驱动的方式&#xff08;如使用消息队列&#xff09;。以…...

FPGA为何要尽量减少组合逻辑的使用

在FPGA设计中&#xff0c;组合逻辑的使用确实需要谨慎&#xff0c;尤其是要尽量减少它的复杂性。这并不是因为组合逻辑本身不好&#xff0c;而是因为它在实际应用中容易引发一系列问题&#xff0c;而这些问题往往与FPGA的设计哲学和硬件特性相冲突。让我从几个关键点来和你聊聊…...

六十天前端强化训练之第十七天React Hooks 入门:useState 深度解析

欢迎来到编程星辰海的博客讲解 看完可以给一个免费的三连吗&#xff0c;谢谢大佬&#xff01; 目录 一、知识讲解 1. Hooks 是什么&#xff1f; 2. useState 的作用 3. 基本语法解析 4. 工作原理 5. 参数详解 a) 初始值设置方式 b) 更新函数特性 6. 注意事项 7. 类组…...