TwinCAT3与MATLAB慢琢玉_TE1410_示例1_将MATLAB作为ADS客户端
wptr33 2025-03-26 17:49 13 浏览
本文需要用到: 1.TE1410(怎么安装可以搜索我的专门文章) 2.TwinCAT3 (怎么安装可以搜索我的专门文章) 3.MATLAB 4.VisualStudio 注意:各个软件的相对版本是有要求的(具体要求可以搜索我的专门文章)
转载需注明出处。全平台同名【工控前哨站】,感谢您的【点赞】、【转发】、【关注】后解锁更多有用、有意思的【工控新知识】。
1 写在前面
MATLAB既可以作为ADS客户端,也可以作为ADS服务器。在这篇文章中,我们将重点介绍MATLAB作为ADS客户端的应用。在MATLAB中,ADS客户端可以通过ADS端口对象与TwinCAT3进行通信。ADS端口对象提供了一组方法,用于与TwinCAT3进行通信,包括读取和写入PLC变量,启动和停止ADS通知等。
2 激活并运行1个TwinCAT工程
在目标系统上打开TwinCAT工程文件激活配置并运行,我们这里就选用示例附件中所提供的TwinCAT工程。当然,你也可以花个3分钟自己写一个,本文的重点在于理解TwinCAT3与MATLAB/Simulink的通讯接口的使用方法。
本脚本建议运行在MATLAB的实时脚本,也可以运行在MATLAB的普通脚本中,只是选择后者的话,相关的输出将会在命令行窗口中,不如前者方便。
3 创建1个本地ADS端口对象
% 获取本地ADS端口对象。
localPort = TwinCAT.ADS.Port();
% 显示本地地址的字符串表示形式
localPort.LocalAmsAddr.AddrString
4 列出所有已注册的TwinCAT ADS路由
% 获取本地端口的路由集合
routes = localPort.Routes;
% 将路由集合以表格的形式列出
routeTable = table(routes.Names', routes.NetIds','VariableNames',{'Target Name', 'AmsNetId'})
5 在选择的目标系统上列出所有可使用的端口
% 如果工作区中没有指定路由目标,则默认为本地
if ~exist('targetId','var')
targetId = "Local";
end
% 根据路由目标获取远程路由对象
remoteRoute = localPort.Routes.LookupItem(targetId);
% 获取远程路由对象的所有可用端口的集合
remotePorts = remoteRoute.Ports;
% 将端口集合以表格的形式列出
remotePortsTable = table(remotePorts.Names', remotePorts.PortNumbers','VariableNames',{'Port Name', 'Port Number'})
6 可选地创建目标端口连接对象
在本例中,我们使用ADS端口“Port_851”(端口号851)访问PLC对象的变量。获取端口连接对象有2种不同的方法:
6.1 方式1:在端口集合中查找端口
该方式更适合于需要动态浏览到目标端口的情况。
plcPort = remotePorts.LookupItem(851);
6.2 方式2:使用带NetId和端口标识符的GetPortConnection
该方式更适合于已经知道NetId和端口号的情况,因为此方式不需要提前获取整个端口集合。
plcPort = localPort.GetPortConnection(strcat(targetId,':851'));
7 目标端口连接对象的2个比较有用的属性
% 目标端口连接对象的地址,可用于ADS读/写命令
remoteAddr = plcPort.AmsAddr;
% 以微秒为单位的任务循环时间(后续的版本可能会对此做出修改)
plcPort_CycleTime = plcPort.TaskCycleTime;
8 可变地址
有3种不同的方法来寻址ADS符号(本例中为PLC变量),当面对读取命令时同理。本节中的所有写命令都将向PLC主程序“MAIN”写入一个新值。变量“fMyDouble”的类型为LREAL对应于MATLAB中的数据类型“double”。
8.1 选项1:通过本地端口对象进行读或写
这种方式不需要配置目标路由和目标端口连接对象,但需要一个相对长的变量地址规范,包含目标NetId,目标端口和符号名称(或符号名称的索引组和偏移量)。 该方式更适合于只需要访问同一目标上的几个变量的情况。
varId11 = {targetId,'Port_851','MAIN.fMyDouble'}; % 元胞数组形式
varId12 = {targetId,851,'MAIN.fMyDouble'}; % 元胞数组形式
varId13 = strcat(targetId,':851/MAIN.fMyDouble'); % 字符串形式
varId14 = {targetId,851,123,456}; % 元胞数组形式,并假设索引组123和索引偏移量456
varId15 = strcat(targetId,':851/123/456'); % 字符串形式,并假设索引组123,索引偏移量456
varId16 = {remoteAddr,'MAIN.fMyDouble'}; % 包含远程地址对象的元胞数组形式
localPort.Write(varId11,double(11.11)); % 将值11.11写入目标端口连接对象
localPort.Write(varId12,double(12.12)); % 将值12.12写入目标端口连接对象
localPort.Write(varId13,double(13.13)); % 将值13.13写入目标端口连接对象
localPort.Write(varId16,double(16.16)); % 将值16.16写入目标端口连接对象
fMyDoubleValue = localPort.Read(varId16,[]) % 从目标端口连接对象读取当前在线值
8.2 选项2:通过目标端口对象读或写
当已经创建好了目标端口连接对象,此种情况需要相对短一点的变量地址规范,只包含符号名称(或符号名称的索引组和偏移量)。 该方式更适合于如果需要访问分布在同一目标系统上不同端口的多个变量的情况。
varId21 = {'MAIN.fMyDouble'}; % 元胞数组形式
varId22 = 'MAIN.fMyDouble'; % 字符串形式
varId23 = {123,456}; % 元胞数组形式,并假设索引组123和索引偏移量456
varId24 = strcat('123/456'); % 字符串形式,并假设索引组123,索引偏移量456
plcPort.Write(varId21,double(21.21)); % 将值21.21写入目标端口连接对象
plcPort.Write(varId22,double(22.22)); % 将值22.22写入目标端口连接对象
fMyDoubleValue = plcPort.Read(varId22,[]) % 从目标端口连接对象读取当前在线值
8.3 选项3:通过变量句柄对象读或写
此种方式必须提前创建一个变量句柄对象。这可以通过本地端口连接对象或远程端口连接对象来完成。其中变量句柄对象保留了一些信息(如内部ADS句柄)以供进一步使用,从而减少了写或读命令本身的持续时间。 该方式更适合于需要多次访问同一个变量的情况。
% 通过本地端口对象(地址来自选项 1)获取一个变量句柄对象
varHandle31 = localPort.GetVarHandle(varId11);
% 通过远程端口对象(其地址来自选项 2)获取一个变量句柄对象
varHandle32 = plcPort.GetVarHandle(varId22);
varHandle31.Write(double(31.31)); % 将值31.31写入变量句柄对象
varHandle32.Write(double(32.32)); % 将值32.32写入变量句柄对象
fMyDoubleValue = varHandle32.Read([]) % 从变量句柄对象读取当前在线值
9 数据类型
MATLAB 和 TwinCAT 中的数据类型存在一定程度的差异。因此,在解析传输过来的数据时,可能会需要进行一些转换操作。 MATLAB数据类型参考请点这里。 TwinCAT数据类型参考请点这里。 在将数据从 MATLAB 转移到 TwinCAT 或反向操作时,需要数据类型信息才能正确解读数据。在很多情况下,隐式类型定义足以实现正确的数据解读。但在某些情况下,显式类型定义是有用的,甚至可能是必需的。
9.1 隐式类型信息
隐式类型信息可以是以下两种形式。 1.传递给写入方法的值的数据类型(MATLAB 端)
varHandle32.Write(double(32.32)) % 写入一个 double 类型的值
2.通过 ADS 符号信息提供的类型信息(TwinCAT 端)
varValueImplicit = varHandle32.Read([])
9.2 显式类型信息
同样也可以提供显式类型信息。 1.作为读取方法的占位值(MATLAB 端)
varValueExplicit = varHandle32.Read(double(0))
varValueReinterpret = varHandle32.Read(uint64(0))
2.通过创建一个值容器对象
% 用 TwinCAT 类型名称初始化值容器
uint64Containter = localPort.CreateValueContainer('ULINT');
uint64Containter.Value = 5
varHandle32.Write(uint64Containter);
varValueReinterpret2 = varHandle32.Read(uint64Containter)
% 具有显示类型信息的值容器也可以通过变量句柄对象的隐式类型信息来进行初始化。
doubleContainer = localPort.CreateValueContainer(varHandle32);
% 写入容器中的值(如果可能的话)总是会被转换为它创建时所对应的类型。
doubleContainer.Value = '4'
class(doubleContainer.Value) %double
doubleContainer.Value = uint32(8) % 入容器中的值(如果可能的话)总是会被转换为它创建时所对应的类型。
class(doubleContainer.Value) %double
10 示例
% 使用变量句柄写读UDINT (uint32)
nMyUDINT_h = plcPort.GetVarHandle('Main.nMyUDINT')
nMyUDINT_h.Write(uint32(42))
udintValue1 = nMyUDINT_h.Read(uint32(0))
udintValue2 = nMyUDINT_h.Read([])
udintValueAsSingle = nMyUDINT_h.Read(single(0))
% 使用变量句柄写REAL (single)
fMyFloat_h = plcPort.GetVarHandle('Main.fMyFloat')
fMyFloat_h.Write(single(42.42))
% 字符串
localPort.Write({remoteAddr,'MAIN.sMyString'},[uint8('This is a String'),uint8(0)]); % 以字节数组的形式写入,以 0 结束
localPort.Read({remoteAddr,'MAIN.sMyString'},[])
stringContainer = localPort.CreateValueContainer('STRING(35)','This is another String') % 创建一个类型化的值容器
localPort.Write({remoteAddr,'MAIN.sMyString'},stringContainer);
stringContainer.Value = 'And this is a third string'
localPort.Write({remoteAddr,'MAIN.sMyString'},stringContainer);
localPort.Read({remoteAddr,'MAIN.sMyString'},[])
% 宽字符
stringContainer = localPort.CreateValueContainer('WSTRING','This is a WString')
localPort.Write({remoteAddr,'MAIN.sMyWString'},stringContainer);
localPort.Read({remoteAddr,'MAIN.sMyWString'},[])
% 布尔
localPort.Write({remoteAddr,'MAIN.bMyBool'},true)
localPort.Write({remoteAddr,'MAIN.bMyBool'},false)
% 字节
localPort.Write({remoteAddr,'MAIN.nMyByte'},uint8(244));
% 字
localPort.Write({remoteAddr,'MAIN.wMyWord'},uint16(13));
% 包含10个REAL数据的数组
localPort.Write({remoteAddr,'MAIN.aMyArray'},single([1,2,3,4,5,6,7,8,9,10]));
arrayValueBytes = localPort.Read({remoteAddr,'MAIN.aMyArray'},[]) % 默认情况下,返回的读取值是以字节数组的形式呈现的。
arrayValue1 = typecast(arrayValueBytes,'single'); % 重新解释字节数组的值
arrayValue2 = localPort.Read({remoteAddr,'MAIN.aMyArray'},zeros(1,10,'single')) % 通过提供一个虚拟值来使用内部类型转换
% 结构体
% 依次写入属性
localPort.Write({remoteAddr,'MAIN.sMyStruct._diField1'},int32(42));
localPort.Write({remoteAddr,'MAIN.sMyStruct._byField2'},uint8(42));
11 ADS 通知
ADS 通知是一种从 TwinCAT 读取值的替代方式,与通过读取请求读取相比,其主要优势在于 TwinCAT 会缓冲这些值,并按照所需的采样时间进行缓冲,并在读取新值时进行通知,同时还附带详细的时间戳。 如果要读取一系列值的时间序列,则使用通知而非通过重复的读取请求进行轮询更为可取。
11.1 创建通知缓冲区,启动通知,停止并评估(在 500 毫秒后)
sampleTime = 10000; % 采样时间同样以100ns为单位
delayTime_100ns = 100000; % 以 100 纳秒为单位的延迟时间(这是 TwinCAT 在将采集到的数据发送至 MATLAB 之前缓冲这些值所花费的时间)
bufferSampleCount = 1000; % 缓冲区的长度以样本计数来表示
notebuffer = localPort.CreateNotificationBuffer({remoteAddr,'MAIN.fbScope.Sinus_SqFast'},[],sampleTime,delayTime_100ns,bufferSampleCount,true,3,int32(5000));
notebuffer.StartNotifications(); % 启动通知
pause(.5); % 等待 500 毫秒
notebuffer.StopNotifications(); % 停止通知
[t,v,overflowSamples,missingSamples,bufferUsage] = notebuffer.ReadBufferEx(); % 读取缓冲区
figure('Name','Read ring buffer content afer notifications were stopped manually'); % 创建一个新的图形窗口
plot(double(t-t(1))/10000000,[v{:}]); % 绘制图形
grid on; % 打开网格
11.2 使用新的缓冲区启动通知,并等待其完全填满
sampleTime = 1e-3; % 采样时间(以秒为单位)
measurementTime = 0.6; % 测量时间(以秒为单位)
% 转换为 ADS 接口数据
Ts = sampleTime/100e-9; %将采样时间转换为 100 纳秒的单位
DelayTime = Ts * 10;
BufferSize = round(measurementTime/sampleTime);
% 样本时间单位为 100 纳秒
% 延迟时间单位为 100 纳秒
% 缓冲区大小为样本数量
% 标志位 : 环形缓存 是/否
notebuffer2 = localPort.CreateNotificationBuffer({remoteAddr,'MAIN.fbScope.Sinus_SqFast'},[],Ts,DelayTime,BufferSize,false);
notebuffer2.StartNotifications(); % 启动通知
waitTimeout = 2000;
try
notebuffer2.WaitFilled(waitTimeout); % 等待缓冲区完全填满
msg = 'Buffer filled successfully';
catch e
msg = ['Buffer was not filled within the last ' num2str(waitTimeout) 'ms: ' e.message];
end
disp(msg);
figure('Name','Read buffer after completely filled and stopped automatically'); % 创建一个新的图形窗口
[t3,v3,overflowSamples_,missingSamples_,bufferUsage] = notebuffer2.ReadBufferEx(); % 读取缓冲区
plot(double(t3-t3(1))/10000000,[v3{:}]); % 绘制图形
hold on; % 保持图形
hold off; % 释放图形
grid on; % 打开网格
12 写在最后
本文主要介绍了如何使用 MATLAB 与 TwinCAT3 之间的 ADS 通讯接口,以及如何读取和写入 PLC 变量。在实际应用中,我们可以通过这种方式实现更多的功能,比如实时监控、数据采集、远程控制等。当然,这只是一个开始,希望能够帮助到大家。如果有任何问题,欢迎关注、留言、讨论,您的支持是我写作的最大动力。
转载需注明出处。全平台同名【工控前哨站】,感谢您的【点赞】、【转发】、【关注】后解锁更多有用、有意思的【工控新知识】。
相关推荐
- 每天一个编程技巧!掌握这7个神技,代码效率飙升200%
-
“同事6点下班,你却为改BUG加班到凌晨?不是你不努力,而是没掌握‘偷懒’的艺术!本文揭秘谷歌工程师私藏的7个编程神技,每天1分钟,让你的代码从‘能用’变‘逆天’。文末附《Python高效代码模板》,...
- Git重置到某个历史节点(Sourcetree工具)
-
前言Sourcetree回滚提交和重置当前分支到此次提交的区别?回滚提交是指将改动的代码提交到本地仓库,但未推送到远端仓库的时候。...
- git工作区、暂存区、本地仓库、远程仓库的区别和联系
-
很多程序员天天写代码,提交代码,拉取代码,对git操作非常熟练,但是对git的原理并不甚了解,借助豆包AI,写个文章总结一下。Git的四个核心区域(工作区、暂存区、本地仓库、远程仓库)是版本控制的核...
- 解锁人生新剧本的密钥:学会让往事退场
-
开篇:敦煌莫高窟的千年启示在莫高窟321窟的《降魔变》壁画前,讲解员指着斑驳色彩说:"画师刻意保留了历代修补痕迹,因为真正的传承不是定格,而是流动。"就像我们的人生剧本,精彩章节永远...
- Reset local repository branch to be just like remote repository HEAD
-
技术背景在使用Git进行版本控制时,有时会遇到本地分支与远程分支不一致的情况。可能是因为误操作、多人协作时远程分支被更新等原因。这时就需要将本地分支重置为与远程分支的...
- Git恢复至之前版本(git恢复到pull之前的版本)
-
让程序回到提交前的样子:两种解决方法:回退(reset)、反做(revert)方法一:gitreset...
- 如何将文件重置或回退到特定版本(怎么让文件回到初始状态)
-
技术背景在使用Git进行版本控制时,经常会遇到需要将文件回退到特定版本的情况。可能是因为当前版本出现了错误,或者想要恢复到之前某个稳定的版本。Git提供了多种方式来实现这一需求。...
- git如何正确回滚代码(git命令回滚代码)
-
方法一,删除远程分支再提交①首先两步保证当前工作区是干净的,并且和远程分支代码一致$gitcocurrentBranch$gitpullorigincurrentBranch$gi...
- [git]撤销的相关命令:reset、revert、checkout
-
基本概念如果不清晰上面的四个概念,请查看廖老师的git教程这里我多说几句:最开始我使用git的时候,我并不明白我为什么写完代码要用git的一些列指令把我的修改存起来。后来用多了,也就明白了为什么。gi...
- 利用shell脚本将Mysql错误日志保存到数据库中
-
说明:利用shell脚本将MYSQL的错误日志提取并保存到数据库中步骤:1)创建数据库,创建表CreatedatabaseMysqlCenter;UseMysqlCenter;CREATET...
- MySQL 9.3 引入增强的JavaScript支持
-
MySQL,这一广泛采用的开源关系型数据库管理系统(RDBMS),发布了其9.x系列的第三个更新版本——9.3版,带来了多项新功能。...
- python 连接 mysql 数据库(python连接MySQL数据库案例)
-
用PyMySQL包来连接Python和MySQL。在使用前需要先通过pip来安装PyMySQL包:在windows系统中打开cmd,输入pipinstallPyMySQL ...
- mysql导入导出命令(mysql 导入命令)
-
mysql导入导出命令mysqldump命令的输入是在bin目录下.1.导出整个数据库 mysqldump-u用户名-p数据库名>导出的文件名 mysqldump-uw...
- MySQL-SQL介绍(mysql sqlyog)
-
介绍结构化查询语言是高级的非过程化编程语言,允许用户在高层数据结构上工作。它不要求用户指定对数据的存放方法,也不需要用户了解具体的数据存放方式,所以具有完全不同底层结构的不同数据库系统,可以使用相同...
- MySQL 误删除数据恢复全攻略:基于 Binlog 的实战指南
-
在MySQL的世界里,二进制日志(Binlog)就是我们的"时光机"。它默默记录着数据库的每一个重要变更,就像一位忠实的史官,为我们在数据灾难中提供最后的救命稻草。本文将带您深入掌握如...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
面试官:git pull是哪两个指令的组合?
-
git 执行pull错误如何撤销 git pull fail
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
-
- 每天一个编程技巧!掌握这7个神技,代码效率飙升200%
- Git重置到某个历史节点(Sourcetree工具)
- git工作区、暂存区、本地仓库、远程仓库的区别和联系
- 解锁人生新剧本的密钥:学会让往事退场
- Reset local repository branch to be just like remote repository HEAD
- Git恢复至之前版本(git恢复到pull之前的版本)
- 如何将文件重置或回退到特定版本(怎么让文件回到初始状态)
- git如何正确回滚代码(git命令回滚代码)
- [git]撤销的相关命令:reset、revert、checkout
- 利用shell脚本将Mysql错误日志保存到数据库中
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)