Matlab Simulink RS-485 Modbus RTU协议串口通信实录

目录

一、目标实现

二、基本原理

三、过程实录

一、基本原理


一、目标实现

最近需要进行一个大型试验,需要实现:通过上位机来实时收集波高仪数据并经过一系列复杂算法来控制下位机(IO模块即继电器)从而控制电磁铁。网上资料十分零散,以此来总结并记录一下过程,也给大家一些借鉴,少走一些弯路(哭)。我是用的MATLAB进行串口通讯,对比了其他串口实时通讯工具,发现MATLAB有代码简单、处理复杂算法能力强的优势,果断选择MATLAB作为全套数据采集及处理的工具!

二、基本原理

三、过程实录

一、测力计通讯(按字节中断)

1.首先是搞清楚上位机(电脑)是如何与硬件进行双向通讯的。当这个涉及到串口通信知识,有许多协议和概念需要搞清楚的:IO模块、Modbus协议、进制转换、RS-485通信、Labview控制等等。有一些工具需要下载的,例如:虚拟串口工具、串口调试工具等等。

(17条消息) STM32作为从机通过RS485实现Modbus RTU通讯_Dobolong的博客-CSDN博客_stm32实现485通信https://blog.csdn.net/DBLLLLLLLL/article/details/88390677?spm=1001.2014.3001.5502在这里只给出该文中几个比较重要的概念:数据帧可以理解为一段能够表达通讯需求的若干个字节。即在产品说明书中所看到的指令即为一个数据帧。RS-485属于半双工通信,即该串行通讯既可以收,也可以发,但是同一时刻下,只能进行收或发。且接口电路中没有专门传递时钟信号的传输线,因此只能采用异步的串行通讯的方式进行收发。在串行通信的时候,是一位一位数据排着队进行传输的,对于发送一个字节(byte),即发送8位。当传输一个字节的时候,需要有起始位或终止位供检测来表示已发送完毕,因此实际上可能会有10位或11位。这个是串行通信规定的传输方式。对于串口这里所有的设置只决定了是否能够正常通信以及通信的速度,其与命令帧内容无关。

对于Modbus协议来说,其规定了命令帧的内容,可以看到(见上述链接)一条主机发送的命令帧一共有8个字节(对于普通485通信,其命令帧长度其实不受限)。每个字节是8位二进制数(如11111000),其表示比较长,因此一般都用16进制来表示。本次实验不涉及从机响应的数据帧,因此就不介绍了。可以简单留意一下,返回的字节总数应为寄存器个数的两倍。我用到的校验方式有两种:一种是校验和,一种是CRC校验。校验和其实定义比较自由,因为只需比较发送指令和接受指令的校验和,若相等则接收的数据正确,不相等的话接收的数据错误。如下图所示,是一个我正在使用的仪器的使用说明书,利用CheckSum校验计算器,输入DA 05 01 02 00(HEX),即可得到校验和E2。

累加和(CheckSum)校验在线计算-ME2在线工具 (metools.info)http://www.metools.info/code/c128.html

CRC校验通常有一些产品说明书里面会自带的,如果没有,可以通过下面的网址去进行查阅对应命令的Modbus CRC-16校验码。串口通信时,需要发校验字节,校验和是最为简单的,就是在发送一帧数据时,把发送的多字节数据相加得到和,但只保留8位,作为一个字节的校验和再发送出去。接收一方在接收到数据后,也按相同的方法求和,也得一个校验和,就与接收的校验和比较,如果相同,接收不是正常的,反之,接收就是错误的。不作处理。

On-line CRC calculation and free library - Lammert BiesOn-line CRC calculation sheet. Free CRC routines downloadable. Covers CRC-16, CRC-32, CRC-CCITT, DNP, Sick and other routines.https://www.lammertbies.nl/comm/info/crc-calculation对于普通485通信来说,一般的采集仪器都是采用这个。对于如何解析响应返回来的指令中的数据,只需要将十六进制数转换为日常使用的十进制,再用相应的单位去变换即可得到。下面是Labview的串口通信简图。

2.然后使用Labview的时候发现,如果是简单的程序,通过Matlab Script还可以直接调用Matlab程序来避免使用繁杂的模块来表示。但是如果调用的程序比较复杂,想调用Matlab里面比较复杂的算法是行不通的。以下这位专家给出了例证。

Matlab与Labview混合编程(一) - 腾讯云开发者社区-腾讯云 (tencent.com)https://cloud.tencent.com/developer/news/368191当程序包含其他.mat文件或者调用自己定义的函数,系统就会报错,显示未定义函数或者打不开该文件。使用比想象中复杂和麻烦很多,因为labview和matlab不完全兼容,例如在matlab代码生成时,若选择输入矩阵大小为:inf x :inf,在vs2015内编译会报错诸如此类的问题。经常会报错且卡死,需要一条条看。(反正我在Labview2020中打不开Matlab2021b的Command窗口)其他的方法诸如DLL调用,和COM组件技术(第二篇)没有继续更深的研究,主要原因还是网上这些文章的针对性,特殊性太强,换个其他算法生成的DLL文件,就可能调用失败,或者无法解除正确的结果。有以下几种方法:ActiveX自动化技术、动态连接库(DLL)技术、MATLAB Script节点法。

①ActiveX是微软公司推出的一个技术集的统称,其基础是组件对象模型COM,Labview作为一个客户端来支持ActiveX自动化技术。

②动态连接库(DLL)是基于 Windows程序设计 的一个重要组成部分.DLL是一个位于程序外部的 过程库,它可以从应用程序中调用和共享.因此,在 LabVIEW 和MATLAB之间应用 DLL可以实现数据传输和函数调用应用。

③MATLAB Script节点法是实现 LabVIEW 和MATLAB间通讯和混合编程的常用方法.这种 方法容易实现,打开脚本速度快,可满足多输入多 输出,信息处理量大.但该方法不能控制 MATLAB 服务器,当节点脚本执行完毕后,MATLAB程序不 能自动关闭。可以Labview考虑到兼容性就不考虑使用Labview了,而是重新考虑使用Matlab。后来考虑到许多控制算法都在Matlab上,并且极为复杂。虽然Labview有自带的ActiveX,

二、波高仪通讯(按ASCII字符中断)

3.Matlab串口通信

(发送指令)

下面结合该作者的代码进行三种发送指令的代码的编写,第一种是只发送一次Modbus指令,第二种是实现连续发送Modbus指令,第三种是实现发送普通485指令。代码分为两个部分,一个是主函数,一个是回调函数。跟普通的接受串口数据绘制图标不同,我需要连续的发送命令帧来一次次地获取波高信息。另外对于与继电器模块的通信,不能够通过连续地发送“点动”命令帧来控制电磁铁,但因为“点动”命令帧之间肯定有间隔,应该采用自锁模式,输入一次命令帧即可。

(17条消息) Matlab使用串口进行数据通信_乌云携月的博客-CSDN博客_matlab串口通信https://blog.csdn.net/weixin_46943050/article/details/125043363

注意在这里发送 

%% Modbus发送单次指令
delete(instrfindall)
scom = serial('COM3');    %建立串口对象函数(需要手动和自己电脑的端口匹配)
fclose(scom);               %关闭串口设备对象,关闭了才能进行配置
scom.InputBufferSize =512;%输入缓冲区
scom.OutputBufferSize =512;%输出缓冲区
scom.ReadAsyncMode = 'continuous';%异同通信模式下,读取串口数据采用连续接收数据方式,下位机返回数据自动存入输入缓冲区中。
scom.BaudRate  = 9600;%设置波特率
scom.Parity = 'none';%无校验位
scom.StopBits  = 1;%1个停止位
scom.DataBits  = 8;%8个数据位
scom.FlowControl  = 'none';%流控
scom.timeout  = 1.0;%一次操作超时时间
scom.BytesAvailableFcnMode =  'byte';%数据读入格式,
% scom.byte = 24;    %Modbus里面一个命令帧是8个字节,但先用24个试一试
%scom.BytesAvailableFcnMode =  'Terminator';%数据读入格式
%scom.Terminator = 'D';%中断关键字是ASCII的D
scom.BytesAvailableFcnCount  = 1024;%触发中断的数据数量
scom.BytesAvailableFcn  = @callback;%串口接收中断回调函数 
try
 fopen(scom);
catch
 '串口打开失败';
end
% fwrite(scom,(hex2dec(['01'; '03'; '00'; '00'; '00'; '03'; '05']))); %写入数据
fwrite(scom,[01,06,00,06,00,01,hex2dec('A8'),hex2dec('0B')]); %写入数据,这里用到hex2dec是为了将十六进制转换为十进制数,从而能够写入。显示的时候还是以十六进制显示的。
data1 = fread(scom,10); %读取数据,这里默认一个字节等于8位=uint8,Modbus定义的数据帧为10个字节。
fclose(scom);%关闭串口设备对象
delete(scom);%删除内存中的串口设备对象
%% Modbus发送单次指令
delete(instrfindall)
scom = serial('COM3');    %建立串口对象函数(需要手动和自己电脑的端口匹配)
fclose(scom);               %关闭串口设备对象,关闭了才能进行配置
scom.InputBufferSize =512;%输入缓冲区
scom.OutputBufferSize =512;%输出缓冲区
scom.ReadAsyncMode = 'continuous';%异同通信模式下,读取串口数据采用连续接收数据方式,下位机返回数据自动存入输入缓冲区中。
scom.BaudRate  = 9600;%设置波特率
scom.Parity = 'none';%无校验位
scom.StopBits  = 1;%1个停止位
scom.DataBits  = 8;%8个数据位
scom.FlowControl  = 'none';%流控
scom.timeout  = 1.0;%一次操作超时时间
scom.BytesAvailableFcnMode =  'byte';%数据读入格式,
% scom.byte = 24;    %Modbus里面一个命令帧是8个字节,但先用24个试一试
%scom.BytesAvailableFcnMode =  'Terminator';%数据读入格式
%scom.Terminator = 'D';%中断关键字是ASCII的D
scom.BytesAvailableFcnCount  = 1024;%触发中断的数据数量
scom.BytesAvailableFcn  = @callback;%串口接收中断回调函数 
try
 fopen(scom);
catch
 '串口打开失败';
end
% fwrite(scom,(hex2dec(['01'; '03'; '00'; '00'; '00'; '03'; '05']))); %写入数据
fwrite(scom,[01,06,00,06,00,01,hex2dec('A8'),hex2dec('0B')]); %写入数据,这里用到hex2dec是为了将十六进制转换为十进制数,从而能够写入。显示的时候还是以十六进制显示的。
data1 = fread(scom,10); %读取数据,这里默认一个字节等于8位=uint8,Modbus定义的数据帧为10个字节。
fclose(scom);%关闭串口设备对象
delete(scom);%删除内存中的串口设备对象

在发送指令时,其需要将BytesAvailableFcnMode中更改为terminator,对应地,Terminator也需要更改为对应的帧头。通过查阅产品手册,发现在接收的字节中帧头第一个字母是’D’,所以采用关键字中断模式,中断标志是’D’。 

%% 发送普通485指令
delete(instrfindall)
scom = serial('COM3');    %建立串口对象函数(需要手动和自己电脑的端口匹配)
fclose(scom);               %关闭串口设备对象,关闭了才能进行配置
scom.InputBufferSize =512;%输入缓冲区
scom.OutputBufferSize =512;%输出缓冲区
scom.ReadAsyncMode = 'continuous';%异同通信模式下,读取串口数据采用连续接收数据方式,下位机返回数据自动存入输入缓冲区中。
scom.BaudRate  = 9600;%设置波特率
scom.Parity = 'none';%无校验位
scom.StopBits  = 1;%1个停止位
scom.DataBits  = 8;%8个数据位
scom.FlowControl  = 'none';%流控
scom.timeout  = 1.0;%一次操作超时时间
scom.BytesAvailableFcnMode =  'byte';%数据读入格式,
% scom.byte = 24;    %Modbus里面一个命令帧是8个字节,但先用24个试一试
%scom.BytesAvailableFcnMode =  'Terminator';%数据读入格式
%scom.Terminator = 'D';%中断关键字是ASCII的D
scom.BytesAvailableFcnCount  = 1024;%触发中断的数据数量
scom.BytesAvailableFcn  = @callback;%串口接收中断回调函数 
try
 fopen(scom);
catch
 '串口打开失败';
end
% fwrite(scom,(hex2dec(['01'; '03'; '00'; '00'; '00'; '03'; '05']))); %写入数据
fwrite(scom,[01,06,00,06,00,01,hex2dec('A8'),hex2dec('0B')]); %写入数据,这里用到hex2dec是为了将十六进制转换为十进制数,从而能够写入。显示的时候还是以十六进制显示的。
data1 = fread(scom,10); %读取数据,这里默认一个字节等于8位=uint8,Modbus定义的数据帧为10个字节。
fclose(scom);%关闭串口设备对象
delete(scom);%删除内存中的串口设备对象

(接受指令)

在前文中提到,由于RS-485是半双工,并不能同时在一个串口内收/发数据(全双工),因此我需要两个串口,一个串口接收数据(COM2),一个串口发送数据(COM1),两个串口同步进行。这里我接收的数据是一个数据帧24个字节,发送的数据是8个字节。在Matlab上实现Modbus协议通信和普通的485通信其实是有区别的,普通的485通信一般是带帧头的,帧头也是用十六进制表示的。在接受仪器返回的指令时,其需要将BytesAvailableFcnMode中更改为terminator,对应地,Terminator也需要更改为对应的帧头,而且这不适用于Modbus RTU传输(如发送指令代码所示)。通过查阅产品手册,发现在接收的字节中帧头第一个字母是’D’,所以采用关键字中断模式,中断标志是’D’。上位机接收到的多通道数据格式如下图,比如我是五个波高数据,分别为: 219.64    136.25    826.58    647.18    171.89mm。一般波高仪精度是,0.1mm。即我要同时收这5个波高仪的数据,指令应当是:DC

 *如图为集线仪的控制命令

这里global的意思是全局变量,一般来说,通常每个 Matlab® 函数均有各自的局部变量,这些局部变量与其他函数的局部变量和基础工作区的局部变量是分开的。但是,如果多个函数都将特定的变量名称声明为global,则它们都共享该变量的一个副本。在任何函数中对该变量的值做任何更改,在将该变量声明为全局变量的所有函数中都是可见的。

将串口接收到的波高数据输入到Matlab跑神经网络,再将神经网络计算得到的控制指令传给IO模块,来控制电磁铁的吸合。

modbus rtu communication - 帮助中心问答 - MATLAB & Simulink (mathworks.cn)https://ww2.mathworks.cn/support/search.html/answers/1917-modbus-rtu-communication.html?fq%5B%5D=asset_type_name:answer&fq%5B%5D=category:icomm/modbus&page=1

instrreset;
clear all; 
close all; 
clc; 
% remove any remaining serial objects to prevent serial issues

disp('BEGIN PROGRAM');
% Initialize Serial Port Object [s]
s = serial('COM3');
% Specify connection parameters
set(s,'BaudRate',9600,'DataBits',8,'StopBits',1,'Parity','None','Timeout',1);

%Open serial connection
fopen(s);

% Specify Terminator - not used for binary mode (RTU) writing
s.terminator = 'CR/LF';

% Set read mode
set(s,'readasyncmode','continuous');

%Check Open serial connection
s.Status

% RUN and COPY hexadecimal array from SimplyModbus software as example
% SimplyModbus IN  [01 03 00 00 00 03 05 CB] 

% Observe the output as result from SimplyModbus software 
% SimplyModbus OUT [01 03 06 01 90 01 16 00 00 00 81]

request = uint8(hex2dec(['01'; '03'; '00'; '00'; '00'; '03'; '05'; 'CB']));
fwrite(s, request); %start in dec
outdec = fread(s);
outhex = dec2hex(outdec);
outstr = reshape(outhex.',1,[]); %return line string of array hexadecimal e.g.: '010306019000EB0000908D'

SP = hex2dec(outstr(7:10))*0.1 %Variable SetPoint de controler
PV = hex2dec(outstr(11:14))*0.1 %Real temperature PV
MV = hex2dec(outstr(15:18))*0.1 %Variable MV

fclose(s);
delete(s);
clear s
disp('STOP')

clear  
s=serial('com5') %选择串口号  
set(s,'BaudRate',115200,'StopBits',1,'Parity','none');%设置波特率  停止位  校验位  
fopen(s)
for i = 1:300 %循环读取 
  out=fread(s,26,'uint8');%读取 数据个数 与 类型  
  plot(x1,y1,x2,y2,x3,y3,x4,y4);%绘图  
  axis([-2000,2000,0,70000]);%设置x,y轴坐标范围  
  pause(0.01);%每次波高指令之间的间隔时间
  i=i+1;
end
fclose(s)   

之后会发一个关于硬件如何组装的视频,

猜你喜欢

转载自blog.csdn.net/m0_56146217/article/details/126762139
今日推荐