康耐视智能相机(Insight)通过ModbusTCP发送字符串到倍福(BECKHOFF)PLC中

news2025/4/20 8:10:25

文章目录

  • 1.背景
  • 2.分析
  • 3.实现
    • 3.1.PLC的ModbusTCP_Server
      • 3.1.1.安装TF6250-Modbus-TCP
      • 3.1.2.PLC设置
    • 3.2.智能相机的ModbusTCP_Client
      • 3.2.1.了解ModbusTCP的协议
      • 3.2.2.根据协议写代码
        • 3.2.2.1.纯函数代码
        • 3.2.2.2.脚本代码
      • 3.2.3.非脚本处理时的代码逻辑图
      • 3.2.4.关于代码的问题及解答
  • 4.总结

1.背景

目前有个需求,要求康耐视智能相机(Insight)在每次触发完成作业后,将一串字符串通过ModbusTCP发送至倍福的PLC中。此时PLC作为Modbus的server,智能相机作为Modbus的client,智能相机主动发送数据给PLC(写PLC的Modbus的保持寄存器(Holding Registers))。
目前已经实现了,效果如下:
在这里插入图片描述代码我已经上传到这里了【康耐视智能相机ModbusTCP发送字符串代码 】,不需要下载积分。造福大家。

2.分析

主要有两部分的功能要实现:PLC的ModbusTCP_Server的实现、智能相机的ModbusTCP_Client的实现。
有了server和client,通讯起来就没啥问题了。至于其中涉及的实时性的问题,暂时先不考虑。

3.实现

经过查阅资料,倍福PLC作为ModbusTCP Server的设置过程很简单,而智能相机的设置过程则非常麻烦。下面来详细介绍。

3.1.PLC的ModbusTCP_Server

3.1.1.安装TF6250-Modbus-TCP

根据从这里【TwinCAT 3 Modbus TCP使用方法】查阅得到的资料,只要下载并安装TF6250-Modbus-TCP这个软件包即可。
在这里插入图片描述
在这里插入图片描述
TF6250-Modbus-TCP的下载地址为【TF6250 | TwinCAT 3 Modbus TCP】,下载的时候需要先登录倍福的账号,没有的话注册一个即可,就是它要求的密码复杂度比较高,要字母+数字+特殊符号+大小写。
在这里插入图片描述具体安装步骤,可以查看前面提到的那个网站的【TwinCAT 3 Modbus TCP使用方法.docx】
在这里插入图片描述

3.1.2.PLC设置

首先,先激活一下试用
在这里插入图片描述在这里插入图片描述
然后写程序
在这里插入图片描述

PROGRAM MAIN
VAR
	arr1 AT %MB0:ARRAY[1..2] OF WORD; (*保持寄存器 起始地址为12288 (0x3000)*)
	arr2 AT %MB10:ARRAY[1..2] OF BYTE; (**)
	
	str1 AT %MB0: STRING;
	byteArr1 AT %MB0:ARRAY[0..9] OF BYTE;
	byteArr2 AT %MB0:ARRAY[1..10] OF BYTE;
END_VAR

为了方便观察,这里准备了字符串以及byte数组,这样子的话,既可以看到字符串,又可以看到字符串对应的16进制值。
在这里插入图片描述
至此,ModbusTCPServer的设置已经完成了,内存的映射等其他操作,系统已经帮忙处理了。
在这里插入图片描述

这里需要注意的是,modbus寄存器与PLC的地址对应关系。这个关系我们可以通过查阅官方文档【TF6250 | TwinCAT 3 Modbus TCP Default Configuration】得知:
在这里插入图片描述保持寄存器对应的是Output registers
在这里插入图片描述所以我们读写保持寄存器时,地址偏移要设置成12288(0x3000)。前面的文档有说要+1,但是我测试不用加1也行,可能具体得看实际情况吧。
在这里插入图片描述
ok,倍福PLC这边已经设置完成了,这时其实就已经可以用你趁手的Modbus调试工具测试一下与PLC的通讯了。我这边测试的话,貌似slave ID设置成0、1或者其他任意值都可以。
在这里插入图片描述

3.2.智能相机的ModbusTCP_Client

智能相机这边就相当的麻烦了。因为我们用的相机的固件版本比较低,不直接支持Modbus(貌似新的固件版本也只是支持ModbusServer,而非ModbusClient),得靠我们自己通过TCPDevice根据Modbus的协议来手动编码。
在这里插入图片描述行吧,那就自己手动搞呗。

3.2.1.了解ModbusTCP的协议

首先,了解一下ModbusTCP的协议,【Modbus TCP协议说明】、【ModbusTCP数据帧】、【如何看懂Modbus数据帧?】。了解其报文结构是非常重要的,因为只有了解其协议规定,我们才能确定为了达到我们的效果需要发送多长的数据,每个数据又应该是如何取值。
在这里插入图片描述简单地说,我们可以总结modbusTCP的数据帧有以下几个特点

1.与通过串口发送的数据帧相比,不用携带校验码。这个应该是因为底层TCP/IP通讯本身就已经是可靠通讯(自带校验及重发)。挺好,我们不用再用CRC16算法来算效验码了。
2.与通过串口发送的数据帧相比,要在前面增加7个字节的MBAP报文头。
3.报文头中的事务处理标识需要累加。实际上,经过测试,不累加也行。后面的程序我都没有对其累加。

3.2.2.根据协议写代码

ok,我们动手。
经过测试,将字符串转化成需要发送出去的数据这部分,代码有两种实现方式:一种是纯利用智能相机提供的函数进行处理,另外一种是通过js脚本实现。
建议先用纯函数实现,然后再用脚本实现,这样理解起来会更加深刻。

3.2.2.1.纯函数代码

先直接放出程序,再依次解释每个函数的作用。
在这里插入图片描述

左侧文字代码说明
'待发送的字符串EditString(255)字符串控件,可以在运行时输入字符串
'字符串长度Len(J26)获取字符串的长度
'长度是否为奇数Mod(J27,2)判断是否为奇数
'减少最后一个字符的字符串Left(J26, J27-1)原来的字符串移除最后一个字符形成的字符串
'最后一个字符Right(J26,1)原来字符串的最后的那个字符
'偶数长度部分的字符串If(J28,J29,J26)原来字符串的的偶数长度部分的字符串,比如字符串长度为5,那么取前面4个;假如为6,那就全取。
'编码1(偶数部分的字符串)BStringf(0, “%~s”, J31)把字符串编码成Binary结构体
'编码2(2byte的0)BStringf(0, “%h”, 0)长度为2,且两个数据都是0的Binary结构体
'编码3(0,字符)BStringf(0, “%c%s”, 0, J30)长度为2,第1个字节为0,第2个字节为字符串最后一个字符,的Binary结构体
'最终发送的编码(奇数长度时)BStringf(0,“%b%b”,J32,J34)字符长度为奇数时,最终需要发送的Binary数据结构体;偶数部分+0+最后一个字符
'最终发送的编码(偶数长度时)BStringf(0,“%b%b”,J32,J33)字符长度为偶数时,最终需要发送的Binary数据结构体;偶数部分+ 0 + 0
'实际需要发送的编码$If(J28,J35,J36)根据原始字符串的长度是奇数还是偶数,确定发送的Binary数据结构体;
'需要发送的编码长度BLen(J37)实际需要发送的编码的长度
'计算Modbus的数据长度1 + 1 + 2 + 2 + 1 + J381设备ID+1字节功能码+2字节寄存器地址+2字节寄存器数量+1字节寄存器数据长度+寄存器数据本身的长度
'手动触发发送Button(“触发”,-1)按钮,触发发送
'设备TCPDevice(“127.0.0.1”,502,0,4,1000,255)设备,IP:127.0.0.1,端口:502,modbus(二进制)发送
'读写QueryDevice(M29,M30,0,0,0,0,0,M26,0,0x10,0x30,0,0,J38/2,J38,J37)发送数据,且读取接收到的数据。M26是【'计算Modbus的数据长度】;0是slaveID;0x10表示写保持寄存器;0x30 0x00表示PLC的寄存器地址; J38/2表示寄存器的数量,因为我们的数据长度是以字节为单位的,而寄存器是16bit大小的,可以存储2个字节,因此要除以2; J38要写到n个寄存器的数据的大小;,J37,要写到n个寄存器的数据。
3.2.2.2.脚本代码

在这里插入图片描述
部分代码解释和上一小节一样,主要解释一下脚本部分

左侧文字代码说明
''将字符串通过脚本处理得到编码Script($J$47)通过脚本处理字符串。脚本的输入参数为字符串,返回处理完成的Binary结构体

脚本截图
在这里插入图片描述

脚本:

function Script() {
}
module.exports = Script;

function stringToUint8ArrayWithSwap(str) {
  var dataArrLen = str.length;
  if (str.length % 2 !== 0) 
  {
    dataArrLen += 1; // 奇数就补一个0
  }
  else
  {
    dataArrLen += 2; // 偶数就补两个0
  }
 
  var dataArray = new Uint8Array(dataArrLen); // 数据会默认初始化为0
  for (var i = 0; i < str.length; i++) {
    dataArray[i] = str.charCodeAt(i); // 将字符拷贝到数组中
  } 
 
  // 每两个数据一组,交换组中的元素
  for (var i = 0; i < dataArrLen; i += 2) {
    // 交换位置 i 和 i+1 的数字
    [dataArray[i], dataArray[i + 1]] = [dataArray[i + 1], dataArray[i]];
  }

  return dataArray;
}

Script.prototype.run = function (arg0) {
  return stringToUint8ArrayWithSwap(arg0)
}

3.2.3.非脚本处理时的代码逻辑图

在这里插入图片描述

3.2.4.关于代码的问题及解答

1.为啥要根据字符串长度的奇偶来执行不同的处理办法?
因为我们最终执行的modbus功能是写保持寄存器,而保持寄存器的大小为2字节,每次写的话都只能写n个寄存器(n为整数),也就是2n个字节的数据。所以,每次写的数据必须是偶数个数据才能填充完n个寄存器。不允许写半个寄存器。

2.为啥要 BStringf(0, “%~s”, J31) 中要选 %~s这种格式?
因为假如选了%s这种格式的话,数据是按小端发送过去的,但是,PLC那边接收到数据是按大端处理的,这就导致你发了字符串"123456"过去,PLC那边存储且显示出来的是"214365",每两个字节内部互相交换。
在这里插入图片描述

3.既然是大小端的问题,为啥不通过设置 BStringf中的第一个参数来处理?
因为它只对数值型数据,比如double、int等类型起作用,对字符串这种连续、独立、可变长度的类型无效。只能选%~s这种格式来处理。

3.字符长度为奇数时,为啥要将最后一个字符单独拿出来,然后插入一个0,然后再把这个字符补回去。
首先,补0是必须的,因为任何字符串都需要用0来作为结束符,不然无法确定一段字符在何处结束。至于为啥要把这个0补在最后一个字符的前面(插队),还是前面说的大小端的问题。我们发送过去的数据,PLC那边会每两个字节交替存储,比如说问哦我们发了 0x00 0x01两个数据过去,存在PLC寄存器的顺序会自动调整为0x01 0x00,而我们的0x00需要存放在物理地址的高位(也就是后面),因此需要做一个插入操作。

4.总结

通过底层的方式实现数据传输,学是能够学到好多东西,但是就是贼麻烦,且鲁棒性差。不知道为啥Modbus这么通用的功能,康耐视智能相机为啥就是不支持。


参考:
【TwinCAT 3 Modbus TCP使用方法】
【Modbus TCP协议说明】
【ModbusTCP数据帧】
【如何看懂Modbus数据帧?】

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

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

相关文章

【设计模式系列】策略模式(二十四)

一、什么是策略模式 策略模式&#xff08;Strategy Pattern&#xff09;是软件设计模式中的一种行为型模式。它定义了一系列算法&#xff0c;并将每一个算法封装起来&#xff0c;使它们可以互换使用&#xff0c;算法的变化不会影响使用算法的用户。策略模式让算法的变化独立于…

Spark SQL 执行计划解析源码分析

本文用于记录Spark SQL执行计划解析的源码分析。文中仅对关键要点进行提及&#xff0c;无法面面具到&#xff0c;仅描述大体的框架。 Spark的Client有很多种&#xff0c;spark-sql&#xff0c;pyspark&#xff0c;spark- submit&#xff0c;R等各种提交方式&#xff0c;这里以…

(2)Spring Security - 了解UserDetailsService

目录 1.认识UserDetailsService1.1.认识UserDetails1.2.UserDetailsService的默认实现 -- InMemoryUserDetailsManager 2.用户信息存储在MySQL数据库中2.1.添加依赖2.2.配置MySQL和Mybatis2.3.在数据库中添加用户信息2.4.添加数据库实体类2.5.编写Mybatis代码2.6.实现UserDetai…

智能设备安全-固件逆向分析

固件逆向分析实验报告-20241022 使用固件常用逆向分析工具&#xff0c;对提供的固件进行文件系统提取&#xff0c;并记录逆向分析实验过程&#xff0c;提交实验报告&#xff08;报告要求图文并茂&#xff0c;对涉及到的关键步骤附截图说明&#xff09;。具体任务如下&#xff1…

图形编辑器基于Paper.js教程17:图像转gcode前的处理,灰度,黑白,抖动

好久没有正经写博客了&#xff0c;前一段时间一直在备考中级项目管理&#xff0c;再加上项目开发只有自己一个人&#xff0c;每天忙的飞起。有闲暇时间也不想写&#xff0c;其中一部分原因也是因为很多简单问题&#xff0c;AI就能回答的很好。而对复杂的问题&#xff0c;也不是…

AI大模型学习笔记|人工智能的发展历程、智能体的发展、机器学习与深度学习的基本理论

学习链接&#xff1a;冒死上传&#xff01;价值2W的大模型入门到就业教程分享给大家&#xff01;轻松打造专属大模型助手&#xff0c;—多模态、Agent、LangChain、ViT、NLP_哔哩哔哩_bilibili 百度网盘自己整理的笔记&#xff1a; 通过网盘分享的文件&#xff1a;1-人工智能的…

qt 设置系统缩放为150%,导致的文字和界面的问题

1 当我们设置好布局后&#xff0c;在100%的设置里面都是正常的&#xff0c;但是当我们修改缩放为150%后&#xff0c;字体图标&#xff0c;界面大小就出现问题了&#xff0c;这就需要我们设置一些参数。 QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);QCoreAppl…

linux-15 关于shell(十四)printenv,hash,环境变量

此前没有用过linux&#xff0c;可能有些觉得很奇怪&#xff0c;就觉得我们在这敲来敲去命令干什么&#xff1f;为什么不使用双击这种方式来操作&#xff1f;大家知道&#xff0c;在Windows里面&#xff0c;其实我们双击也无非就是告诉我们shell需要将这个命令发送在内核上启动的…

虚拟机如何使用物理机的公私钥

一、生成公私钥&#xff08;如果没有的话&#xff09; 使用如下指令生成 生成RSA公私钥 ssh-keygen 生成EdDSA公私钥 ssh-keygen -t ed25519 Windows目录 linux会直接生成在当前目录下。 二、导出 一般都是从windows系统导入到linux系统。 可以直接将公私钥文件复制到虚拟机…

SpringBoot【十一】mybatis-plus实现多数据源配置,开箱即用!

一、前言&#x1f525; 环境说明&#xff1a;Windows10 Idea2021.3.2 Jdk1.8 SpringBoot 2.3.1.RELEASE 正常情况下我们在开发系统的时候都是使用一个数据源&#xff0c;但是由于有些项目同步数据的时候不想造成数据库io消耗压力过大&#xff0c;便会一个项目对应多个数据源…

前端报错npm ERR cb() never called问题

环境使用node版本v14.21.3&#xff0c;npm版本6.14.18 1.问题描述 1.1使用npm install后报错 npm ERR! cb() never called!npm ERR! This is an error with npm itself. Please report this error at: npm ERR! ? ? <https://npm.community>npm ERR! A complete log…

C++ STL Cookbook STL算法

目录 std::copy 将容器元素合并为一个字符串 使用 std::sort 对容器进行排序 使用 std::transform 修改容器 在容器中查找项目 使用 std::sample 采样数据集 (写在前面&#xff1a;笔者前段时间备战考试和比赛了&#xff0c;现在回来继续更新) STL实际上提供了非常非常丰…

SpringBoot【十】mybatis之xml映射文件>、<=等特殊符号写法!

一、前言&#x1f525; 环境说明&#xff1a;Windows10 Idea2021.3.2 Jdk1.8 SpringBoot 2.3.1.RELEASE 在利用mybatis进行开发的时候&#xff0c;编写sql时可能少不了>、<等比较符号&#xff0c;但是在mapper映射文件中直接使用是不行的&#xff0c;会报错&#xff0…

单元测试SpringBoot

添加测试专用属性 加载测试专用bean Web环境模拟测试 数据层测试回滚 测试用例数据设定

每天40分玩转Django:简介和环境搭建

Django简介和环境搭建 一、课程概述 学习项目具体内容预计用时Django概念Django框架介绍、MVC/MTV模式、Django特点60分钟环境搭建Python安装、pip配置、Django安装、IDE选择45分钟创建项目项目结构、基本配置、运行测试75分钟实战练习创建个人博客项目框架60分钟 二、Djang…

Jenkins参数化构建详解(This project is parameterized)

本文详细介绍了Jenkins中不同类型的参数化构建方法&#xff0c;包括字符串、选项、多行文本、布尔值和git分支参数的配置&#xff0c;以及如何使用ActiveChoiceParameter实现动态获取参数选项。通过示例展示了传统方法和声明式pipeline的语法 文章目录 1. Jenkins的参数化构建1…

Windows安装WSL子系统及docker,以及WSL和docker配置、使用及问题解决

在Windows操作系统中,Ubuntu子系统(也称为Windows Subsystem for Linux, WSL)为开发者提供了一个在Windows环境下运行Linux环境的平台。然而,有时用户在按照Ubuntu子系统或者使用WSL时,可能会遇到各种问题,下面总结一下解决方式。 想要在Windows上安装Docker(实际上是基…

Linux中的线程

目录 线程的概念 进程与线程的关系 线程创建 线程终止 线程等待 线程分离 原生线程库 线程局部存储 自己实现线程封装 线程的优缺点 多线程共享与独占资源 线程互斥 互斥锁 自己实现锁的封装 加锁实现互斥的原理 死锁 线程同步 线程的概念 回顾进程相关概念 …

shell编程(完结)

shell编程&#xff08;完结&#xff09; 声明&#xff01; 学习视频来自B站up主 ​泷羽sec​​ 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章 笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其…

ctfshow-web 151-170-文件上传

151. 我们首先想到就是上传一句话木马。但是看源代码限制了png。 &#xff08;1&#xff09;改前端代码。 这里是前端限制了上传文件类型&#xff0c;那我们就改一下就好了嘛,改成php。 这里直接修改不行&#xff0c;给大家推荐一篇简短文章&#xff0c;大家就会了&#xff08…