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

news2025/3/16 13:03:38

文章目录

  • 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.spelling
                child_name = child.spelling
                if child.type.get_array_size() != -1:  # 数组需要特殊处理
                    prefix = child_type.split('[')[0]
                    child_type = prefix
                    child_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 CursorKind

clang.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 members


def 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 code


def 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.spelling
            if 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语言编程】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2316034.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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;补题搜了一下才解…

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

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

正点原子[第三期]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#的基础语法,包括变量、数据类型、运算符、流程控制、面向对象等,适合没有编程基础的…

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

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

基于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 …

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<…

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

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

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

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

芯科科技推出的BG29超小型低功耗蓝牙®无线SoC,是蓝牙应用的理想之选

具有扩大的内存和超低功耗特性的超小型BG29是互联健康设备的理想之选 低功耗无线领域内的领导性创新厂商Silicon Labs&#xff08;亦称“芯科科技”&#xff0c;NASDAQ&#xff1a;SLAB&#xff09;今日宣布&#xff1a;推出全新的第二代无线开发平台产品BG29系列无线片上系统…

export、export default 和 module.exports 深度解析

文章目录 1. 模块系统概述1.1 模块系统对比1.2 模块加载流程 2. ES Modules2.1 export 使用2.2 export default 使用2.3 混合使用 3. CommonJS3.1 module.exports 使用3.2 exports 使用 4. 对比分析4.1 语法对比4.2 使用场景 5. 互操作性5.1 ES Modules 中使用 CommonJS5.2 Com…

qwen2.5-vl多机多卡分布式部署

记录一下工作中进行多机多卡部署qwen2.5-vl多模态大模型踩过的坑 第一个天坑就是官方提供的镜像qwenllm/qwenvl:2.5-cu121有问题&#xff0c;在titan显卡会抛出cuda error:no kernel image is availabe for execution on the device. 这是cuda内核与GPU不兼容的问题&#xff0c…