目录
Modbus-Ascii详解
- Modbus-ASCII 是 Modbus 协议家族中的一种基于文本的串口通信协议,专为通过 RS-232/RS-485 串口 进行数据传输设计。与二进制格式的 Modbus-RTU 不同,Modbus-ASCII 使用可读的 ASCII 字符表示数据,适合调试场景或需要人工查看数据内容的场合。
介绍
- Modbus ASCII是一种将二进制数据转换为可打印的ASCII字符的通信协议,每个8位数据需要两个ASCII字符表示,报文之间通过特定字符分隔。这种编码方式使得数据在传输过程中更易于阅读和调试。Modbus ASCII协议主要应用于需要串行通信接口进行数据交互的领域,如工业自动化、楼宇自动化、电力系统、环境监测等。尽管其传输效率相对较低,但由于其良好的可读性和容错性,在要求数据可读性或通信链路稳定性较好的场合,Modbus ASCII仍然是一个不错的选择。
- Modbus ASCII的消息帧格式包括起始符号、节点地址、功能码、数据字段和LRC校验码,以及消息的结束符号。具体来说,每个8位的字节被拆分为两个ASCII字符进行发送,例如,十六进制数0xAB会被分解成ASCII字符“A”和“B”进行发送。这种编码方式允许在两个字符之间有最长1秒的时间间隔而不引发通信故障,采用纵向冗余校验(LRC)的方法来检验错误。
- 在应用方面,Modbus ASCII协议允许直接在终端看到可读字符,方便调试和人工解析,尤其在需要人工介入或监控的场景中显得尤为重要。然而,由于其传输效率较低,因此在实时性要求较高的场合,可能会考虑使用Modbus RTU或其他更快捷的协议。此外,为了避免与报文中的特殊字符冲突,需要对特定字符进行转义处理。
- Modbus-ASCII是一种基于ASCII码的Modbus通信协议。数据以ASCII字符形式传输,每个字节由两个ASCII字符表示。
Modbus-ASCII 核心特性
特性 | 说明 |
---|---|
传输方式 | 基于文本的 ASCII 字符传输(可读性强,但效率低) |
物理接口 | RS-232 或 RS-485 串口(默认) |
校验方式 | 纵向冗余校验(LRC),校验范围包括整个数据帧(不含起始/结束符) |
字符编码 | 每个字节(8 位)用 2 个 ASCII 字符表示(例如 0x4B 表示为 "4B" ) |
传输效率 | 较低(因字符转换和冗余校验) |
典型应用 | 调试、人工数据监控、低速率设备通信 |
应用场景
-
调试与监控
-
开发阶段通过串口助手直接查看原始数据帧。
-
快速定位通信故障(如地址错误、校验失败)。
-
-
老旧设备兼容
-
某些早期设备仅支持 ASCII 协议(如部分温控器、仪表)。
-
-
低速通信环境
-
对实时性要求不高的场景(如环境监测传感器)。
-
Modbus-Ascii帧结构
帧结构:起始符号(:)、设备地址、功能码、数据和LRC校验字段。
LRC效验
/// <summary>
/// 将字节数组,通过lrc算法生成效验码
/// </summary>
/// <param name="date">字节数组</param>
/// <returns>效验</returns>
public static string CalcLRC(byte[] date)
{
// 1 获取字节数组每一个元素相加的和
uint sum = (uint)date.Sum(x => x); // 计算每个元素的和
// 2 把sum进行取反操作,再加1,再和0xff进行与运算,
uint res = (~sum +1 )& 0xff;
return res.ToString("X2");
}
将数据字节转成ASCII
/// <summary>
/// [01,03,00,00,00,01] 转成 ":010300010001FB\r\n"
/// </summary>
/// <param name="date">转换的字节数组</param>
/// <returns>转成ascii字符串</returns>
public static string GeRequestFrame(byte[] date)
{
// 1 算1rc效验码
string jym = CalcLRC(date);
string requesData = "";
// 2 遍历字节数组
foreach(byte b in date)
{
requesData += b.ToString("X2");
}
// 3 拼接效验码
string value = ":" + requesData + jym + "\r\n";
return value;
}
将返回帧转为数据字节
/// <summary>
/// 转成对应的ushort数组[5A,C0]
/// </summary>
/// <param name="s">ASCII字符串</param>
/// <param name="value">寄存器个数 一个时候2字节,2个4字节</param>
/// <param name="startIndex">从哪个位置开始截取</param>
/// <returns></returns>
public static ushort[] StringToUshort(string s,int valueCount ,int startIndex=7)
{
// 如果寄存器个数*4+开始截取数据的位置>整体字符串长度 证明没有数据部分
if(valueCount*4+startIndex>s.Length)
{
throw new ArgumentException("字符串长度不满足最小的解析要求");
}
// 正常的响应帧格式
// 定义长度为寄存器个数数组
// :010302 5AC0 5AC0 5AC0 E0
ushort[] bs = new ushort[valueCount];
for(int i = 0; i < valueCount; i++)
{
string value = s.Substring(startIndex,4);
startIndex += 4;
Console.WriteLine(value);
bs[i] = Convert.ToUInt16(value,16);
}
return bs;
}
Modbus-Ascii的实现
namespace Modbus_Ascii
{
public partial class Form1 : Form
{
// 参数1 串口名 , 参数2 波特率 , 参数3 奇偶效验 , 参数4 数据位 , 参数5 停止位
public SerialPort port = new SerialPort("COM2",9600,Parity.None,8,StopBits.One);
public Form1()
{
InitializeComponent();
port.Open();
port.DataReceived += Port_DataReceived;
// 1 验证CalcLRC方法
string s = Tools.CalcLRC(new byte[] {01,03,00,00,00,01});
MessageBox.Show(s);
// 2 验证把字节转成ASCII请求帧方法
string s1 = Tools.GeRequestFrame(new byte[] { 01, 03, 00, 00, 00, 01 });
MessageBox.Show(s1);
string s2 = Tools.GeRequestFrame(new byte[] {01,03,00,00,00,01});
// 发送
port.Write(s2);
}
/// <summary>
/// 接收数据事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
byte[] bs = new byte[port.ReadBufferSize];
port.Read(bs, 0, bs.Length);
string s = Encoding.ASCII.GetString(bs,0,bs.Length);
Console.WriteLine(s);
ushort[] data = Tools.StringToUshort(s,1);
Console.WriteLine(data[0]);
}
}
internal static class Tools
{
/// <summary>
/// 将字节数组,通过lrc算法生成效验码
/// </summary>
/// <param name="date">字节数组</param>
/// <returns>效验</returns>
public static string CalcLRC(byte[] date)
{
// 1 获取字节数组每一个元素相加的和
uint sum = (uint)date.Sum(x => x); // 计算每个元素的和
// 2 把sum进行取反操作,再加1,再和0xff进行与运算,
uint res = (~sum +1 )& 0xff;
return res.ToString("X2");
}
/// <summary>
/// [01,03,00,00,00,01] 转成 ":010300010001FB\r\n"
/// </summary>
/// <param name="date">转换的字节数组</param>
/// <returns>转成ascii字符串</returns>
public static string GeRequestFrame(byte[] date)
{
// 1 算1rc效验码
string jym = CalcLRC(date);
string requesData = "";
// 2 遍历字节数组
foreach(byte b in date)
{
requesData += b.ToString("X2");
}
// 3 拼接效验码
string value = ":" + requesData + jym + "\r\n";
return value;
}
/// <summary>
/// 转成对应的ushort数组[5A,C0]
/// </summary>
/// <param name="s">ASCII字符串</param>
/// <param name="value">寄存器个数 一个时候2字节,2个4字节</param>
/// <param name="startIndex">从哪个位置开始截取</param>
/// <returns></returns>
public static ushort[] StringToUshort(string s,int valueCount ,int startIndex=7)
{
// 如果寄存器个数*4+开始截取数据的位置>整体字符串长度 证明没有数据部分
if(valueCount*4+startIndex>s.Length)
{
throw new ArgumentException("字符串长度不满足最小的解析要求");
}
// 正常的响应帧格式
// 定义长度为寄存器个数数组
// :010302 5AC0 5AC0 5AC0 E0
ushort[] bs = new ushort[valueCount];
for(int i = 0; i < valueCount; i++)
{
string value = s.Substring(startIndex,4);
startIndex += 4;
Console.WriteLine(value);
bs[i] = Convert.ToUInt16(value,16);
}
return bs;
}
}
}
Modbus-ASCII和Modbus-RTU的对比
对比项 | Modbus-ASCII | Modbus-RTU |
---|---|---|
数据格式 | 文本(ASCII 字符) | 二进制(紧凑高效) |
校验方式 | LRC(纵向冗余校验) | CRC(循环冗余校验) |
传输效率 | 低(每个字节需 2 字符表示) | 高(直接传输二进制) |
可读性 | 高(人眼可直接阅读) | 低(需工具解析) |
典型波特率 | 1.2 kbps ~ 19.2 kbps | 9.6 kbps ~ 115.2 kbps |
适用场景 | 调试、低速率通信 | 工业实时控制 |
使用NModbus4实现Modbus-Ascii
在C#中使用第三方库NModbus4进行Modbus通信时,首先需要安装该库。可以通过NuGet包管理器来安装。
实例
public partial class Form1 : Form
{
SerialPort port = new SerialPort("COM2",9600,Parity.None,8,(StopBits)1);
ModbusSerialMaster master;
public Form1()
{
InitializeComponent();
port.Open();
master = ModbusSerialMaster.CreateAscii(port);
}
/// <summary>
/// 读取
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
// 读取寄存器数据
ushort[] data = master.ReadHoldingRegisters(2, 0x000, 3);
MessageBox.Show(data[0] + "-" + data[1] + "-" + data[2]);
}
/// <summary>
/// 写入
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button2_Click(object sender, EventArgs e)
{
// master.WriteSingleRegister(2,7,6); //写入单个寄存器
master.WriteMultipleRegisters(2,3,new ushort[] {10,30,4,32,4,56,99});
}
}