首页 > 编程语言 >C# NModbus RTU串口通信

C# NModbus RTU串口通信

时间:2024-10-22 20:45:01浏览次数:1  
标签:01 Console C# 串口 RTU master 线圈 寄存器

Modbus RTU 串口通信

虚拟串口工具:https://www.virtual-serial-port.org/

Modbus调试工具:https://www.modbustools.com/download.html

NOTE:都是付费软件,但是网上有盗版。

添加两个虚拟串口,这两个虚拟串口是互相连通的:

串口调试工具:https://github.com/SuperStudio/SuperCom

保证两个串口可以互相通信,然后打开Modbus Slave,并连接到COM2:

打开串口调试工具,先手工构造消息,熟悉一下Modbus协议。

NOTE:Modbus协议规范网上有中文版可以下载。

CRC计算工具:https://crccalc.com

位于地址1的设备的数据类型是线圈,现在读取1号设备的前4个线圈数据:

请求:

设备地址 1B 功能 1B 起始地址 2B 读取数量 2B CRC校验码 2B
01 01 00 00 00 04 3D C9

计算器计算出的校验码是 C9 3D,但不能直接用,要将高位字节和低位字节反转,即 3D C9 。

响应:

设备地址 1B 功能 1B 数据个数 1B 线圈状态 NB CRC校验码
01 01 01 08(00001000) 50 4E

根据协议规范:

  • 响应数据数据从低位到高位起,依次对应设备中的线圈寄存器地址。
  • 如果结果数量大于8个,则再加一个字节,新加的字节从低位到高位,依次对应剩余的寄存器地址(8 ~ N)。
  • 如果数据数量不是8的倍数,则在高位补零。

所以可以看到1号设备的前三个寄存器值都是0,第4个(3号地址)值是1。

再试一下,读取1号设备前9个线圈的数据:

设备地址 1B 功能 1B 起始地址 2B 读取数量 2B CRC校验码 2B
01 01 00 00 00 0A BC 0D

响应:

设备地址 1B 功能 1B 数据个数 1B 线圈状态 NB CRC校验码
01 01 02 C8 02(1100 1000 0000 0010) 6F FD

从响应结果来看,前10个寄存器中,地址为3、6、7、9的这四个寄存器值为1,其他都是0 。

看下模拟器是不是这样:

没毛病。注意地址为B的那个寄存器值也是1,但它是第12个寄存器,我们只请求了前10个,所以只给了前十个寄存器的值,剩余的比特位协议规定要补零,因为要按字节对齐。

再试一下发个错误的报文,看下错误响应。

错误类型:校验码错误

错误请求:01 01 00 00 00 0A BC 00

结果:无响应

错误类型:读取线圈数量65535

错误请求:01 01 00 00 FF FF 3D BA

结果:

设备地址 1B 功能码 1B (功能码 + 0x80) 异常码 1B CRC校验码
01 81 03 00 51

Modbus协议规范中的异常码说明:

异常码 异常名称 备注
01 非法的功能码 服务器不认识功能码
02 非法的数据地址 与请求有关
03 非法的数据值 与请求有关
04 服务器故障 执行过程中,服务器故障
05 确认 服务器接收服务调用,但是需要相对长的时间完成服务,
因此,服务器仅返回一个服务调用接收的确认
06 服务器繁忙 服务器不能接受MODBUS请求的PDU,
客户应用有责任决定是否和何时重发请求
0A 网关故障 网关路径是无效的
0B 网关故障 目标设备没有响应时,网关生成这个异常信息

1号设备一共只有16个寄存器,要读取65535个寄存器,得到的响应是03:非法的数据值。

NModbus

NMondbus是C#中Modbus通信协议的一个实现。

安装

打开NuGet控制台,或者包管理器,安装下面两个包。NModbus.Serial 用于串口通信。

PM> Install-Package NModbus
PM> Install-Package NModbus.Serial
官方示例

NMondbus Github仓库地址:https://github.com/NModbus/NModbus.git

官方示例:https://github.com/NModbus/NModbus/blob/master/Samples/Program.cs

串口通信

串口通信中,数据以帧的形式发送,每一帧通常包括:起始位(Start Bit)、数据位(Data Bits)、可选的奇偶校验位(Parity Bit)以及停止位(Stop Bits)。

同步通信
using System.IO.Ports;
using NModbus;
using NModbus.Serial;

var portName = "COM3";
var baudRate = 9600;

var port = new SerialPort();
// 设置串口名
port.PortName = portName;
// 设置波特率
port.BaudRate = baudRate;
// 设置奇偶校验方式,要跟Modbus Slave那边同步,默认是None,我这里改成偶校验了
port.Parity = Parity.Even;
// 停止位
port.StopBits = StopBits.One;

// 打开串口
port.Open();


var factory = new ModbusFactory();
// 创建一个Modbus RTU协议的Master
var master = factory.CreateRtuMaster(port);

// 数组输出为字符串
string ArrayToString<T>(T[] values, string sep = " ")
{
    return string.Join(sep, values.Select(r => Convert.ToString(r)).ToArray());
}

// 随机数对象
var random = new Random();

// 随机Boolean
bool RandomBoolean()
{
    return random.NextDouble() > 0.5;
}

// 随机ushort
ushort RandomUnsignedShort()
{
    return (ushort)random.Next(ushort.MinValue, ushort.MaxValue);
}

try
{
    // 写线圈
    var coilsToWrite = Enumerable.Range(0, 4).Select(i => RandomBoolean()).ToArray();
    Console.WriteLine($"写线圈:{ArrayToString(coilsToWrite)}");
    master.WriteMultipleCoils(1, 0, coilsToWrite);

    // 读线圈
    var coils = master.ReadCoils(1, 0, 4);
    Console.WriteLine($"读线圈:{ArrayToString(coils)}");

    // 写单个线圈 / 写之前
    Console.WriteLine($"读线圈:{ArrayToString(master.ReadCoils(1, 0, 4))}");

    // 写单个线圈
    var coilValue = RandomBoolean();
    master.WriteSingleCoil(1, 1, coilValue);
    Console.WriteLine($"写单个线圈(地址=1):{coilValue}");

    // 写单个线圈
    coilValue = RandomBoolean();
    master.WriteSingleCoil(1, 2, coilValue);
    Console.WriteLine($"写单个线圈(地址=2):{coilValue}");

    // 写单个线圈 / 写之后
    Console.WriteLine($"读线圈:{ArrayToString(master.ReadCoils(1, 0, 4))}");

    // 读输入状态
    var inputs = master.ReadInputs(2, 0, 4);
    Console.WriteLine($"读输入状态:{ArrayToString(inputs)}");

    // 写多个保持寄存器
    var registerValues = Enumerable.Range(0, 4).Select(i => RandomUnsignedShort()).ToArray();
    Console.WriteLine($"写保持寄存器:{ArrayToString(registerValues)}");
    master.WriteMultipleRegisters(3, 0, registerValues);

    // 写单个寄存器 / 写之前
    registerValues = master.ReadHoldingRegisters(3, 0, 4);
    Console.WriteLine($"读保持寄存器:{ArrayToString(registerValues)}");

    // 写单个寄存器
    var value = RandomUnsignedShort();
    Console.WriteLine($"写单个寄存器(地址=1):{value}");
    master.WriteSingleRegister(3, 1, value);

    // 写单个寄存器
    value = RandomUnsignedShort();
    Console.WriteLine($"写单个寄存器(地址=2):{value}");
    master.WriteSingleRegister(3, 2, value);

    // 写单个寄存器 / 写之后
    registerValues = master.ReadHoldingRegisters(3, 0, 4);
    Console.WriteLine($"读保持寄存器:{ArrayToString(registerValues)}");


    // 读多个输入寄存器
    var inputRegisters = master.ReadInputRegisters(4, 0, 4);
    Console.WriteLine($"读输入寄存器:{ArrayToString(inputRegisters)}");
}
catch (SlaveException ex)
{
    Console.WriteLine(ex.Message);
}

// 关闭串口
port.Close();

标签:01,Console,C#,串口,RTU,master,线圈,寄存器
From: https://www.cnblogs.com/kui0112/p/18493692

相关文章

  • vscode+phpstudy+xdebug无法断点(踩坑记)
    参考文档:https://zhuanlan.zhihu.com/p/113171737安装vscode、下载phpstudy最新版这2步都不说了,网上大把教程。本文主要把phpstudy的一个坑点记录一下配置网站配置伪静态location/{if(!-e$request_filename){rewrite^(.*)$/index.php?s=$1las......
  • SD6502 Programming prototyping techniques
    SD6502ProgrammingIIAssignmentIIDueDate:23rdOctober2024,11:59pmTopics(LOs):ApplyprototypingtechniquesApplyeffectiveproblem-solvingstrategiestofosterprogrammingskills.Weighting=25%oftheFinalMark.TotalMarks=100.Group......
  • 近几年CSP-S考点分析与2024预测
    考点概率2020T1模拟、数学、二分一道很好的思维题,当年的T1比T2要难。T2贪心、位运算当年最简单的一道题,思维难度也不高。T3dp、topo要对题目进行转化,变成一个本质相同但难度不同的问题。T4队列、贪心类似于对于题意进行模拟。2021T1堆、贪心直接对整个进行贪心就......
  • P2934 [USACO09JAN] Safe Travel G 题解
    一个用平衡树,不用脑子的写法。(目前没有用平衡树的诶。)题意不经过最短路的最后一条边的最短路,保证最短路唯一。思路看到最短路唯一容易想到建出的最短路DAG其实是最短路树(以\(1\)为根)。那题意转化为求每个节点不经过与父亲的连边,所能到根节点的最短路。容易发现每个点的......
  • 题解:P10977 Cut the Sequence
    题目传送门分析看到这种题就可以想到动态规划,先设状态:$f_i$表示考虑前$i$个数,所需要的最小代价。发现$f_i$可以从所有$i$以前的状态加后一段区间转移过来,于是可以列出状态转移方程:$$f_i=\min_{j=i-1}^{s_i-s_j\leqm}(f_j+\max_{k=j+1}^i)$$其中$j$......
  • NewStar2024-week3-Crypto
    古典密码不想看而且最近很忙,wp就贴exp了Crypto不用谢喵fromCrypto.CipherimportAESfromCrypto.Util.numberimport*importosKEY=b"fake_key_fake_ke"FLAG="flag{fake_flag_fake_flag}"defdecrypt(c):AES_ECB=AES.new(KEY,AES.MODE_ECB)......
  • CSP-S 2020~2023 分析
    2020T1儒略日直接模拟即可,洛谷难度虚高。T2动物园考察对二进制数的理解程度,很简单,但需要特判答案超过longlong范围的情况。T3函数调用就是处理一下拓扑序就行了,代码细节较多,但整体比较简单。T4贪吃蛇很难想的贪心,考虑水分。发现$n\le3$的点很简单,轻松拿下$20......
  • platform_device_register 和platform_driver_register;有些驱动里没有platform_device
    platform_device_register和platform_driver_register是Linux内核中用于注册平台设备和平台驱动程序的函数。为什么很多驱动里没有platform_device_register在Linux内核中,不是所有的驱动程序都需要显式调用platform_device_register函数来注册平台设备。这是因为设备驱动程序可以......
  • 10 问 OB Cloud 云数据库
    随着云计算与人工智能的持续进步,云数据库日益成为企业数据管理的首选,凭借其高可用性、弹性扩展及运维简单等特点,更快地推动了企业的数字化转型步伐。2022年,OceanBase推出了OBCloud云数据库,标志着云数据库战略的正式启动。在随后的两年里,OBCloud迅速赢得了超过700家客户的......
  • P2866 [USACO06NOV] Bad Hair Day S
    [USACO06NOV]BadHairDayS题目描述农夫约翰有NNN头奶牛正在过乱头发节。每一头牛都站在同一排面朝右,它们被从左到右依次编号为......