Modbus-Ascii详解

目录

Modbus-Ascii详解

介绍

Modbus-ASCII 核心特性

应用场景

Modbus-Ascii帧结构

LRC效验

将数据字节转成ASCII

将返回帧转为数据字节

Modbus-Ascii的实现

Modbus-ASCII和Modbus-RTU的对比

使用NModbus4实现Modbus-Ascii

实例


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"
传输效率较低(因字符转换和冗余校验)
典型应用调试、人工数据监控、低速率设备通信

应用场景

  1. 调试与监控

    • 开发阶段通过串口助手直接查看原始数据帧。

    • 快速定位通信故障(如地址错误、校验失败)。

  2. 老旧设备兼容

    • 某些早期设备仅支持 ASCII 协议(如部分温控器、仪表)。

  3. 低速通信环境

    • 对实时性要求不高的场景(如环境监测传感器)。


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-ASCIIModbus-RTU
数据格式文本(ASCII 字符)二进制(紧凑高效)
校验方式LRC(纵向冗余校验)CRC(循环冗余校验)
传输效率低(每个字节需 2 字符表示)高(直接传输二进制)
可读性高(人眼可直接阅读)低(需工具解析)
典型波特率1.2 kbps ~ 19.2 kbps9.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});
    }
}

### Modbus ASCII Mode Protocol and Implementation #### Overview of Modbus ASCII Mode In Modbus communication, the `MB_ASCII` mode represents one of the transmission modes available within the protocol framework[^1]. This mode uses a serial interface to transmit data encoded as ASCII characters. #### Frame Structure in Modbus ASCII Mode The structure of frames in Modbus ASCII differs from other modes like RTU or TCP. Each message begins with a colon (`:`) character (ASCII code 3AH), followed by address field, function code, data fields, error check field, and ends with carriage return line feed (`CRLF`) characters (CR=0DH, LF=0AH)[^3]. - **Start Delimiter:** A single colon (`:`). - **Address Field:** Represents the slave device's address. - **Function Code:** Indicates what action should be performed. - **Data Fields:** Contain actual payload information such as register values. - **Error Check Field:** Typically includes LRC (Longitudinal Redundancy Check). - **End Delimiters:** Carriage Return Line Feed (`\r\n`). #### Example of Sending Data Using Modbus ASCII Mode Below demonstrates how to send a simple request using Python over an ASCII connection: ```python import serial def create_ascii_frame(slave_id, func_code, reg_addr_high, reg_addr_low, num_regs_high, num_regs_low): # Constructing the frame without checksum initially raw_frame = f":{slave_id:02X}{func_code:02X}{reg_addr_high:02X}{reg_addr_low:02X}{num_regs_high:02X}{num_regs_low:02X}" # Calculate LRC lrc = 0xFF & (-sum([int(raw_frame[i:i+2], 16) for i in range(1, len(raw_frame)-1, 2)])) # Append LRC and end delimiters complete_frame = raw_frame + f"{lrc:02X}\r\n" return complete_frame.encode('ascii') ser = serial.Serial('/dev/ttyUSB0', baudrate=9600, parity='E') frame = create_ascii_frame(1, 3, 0, 6, 0, 1) ser.write(frame) response = ser.read_until(b'\r\n')[:-2] # Read until CRLF but strip it off print(f"Response received: {response}") ``` This script constructs a valid Modbus ASCII command packet aimed at reading holding registers from a specific slave ID. It calculates the required Longitudinal Redundancy Check value before appending necessary terminators according to specifications outlined above. --related questions-- 1. How does the calculation of LRC differ between Modbus RTU and ASCII? 2. What are common pitfalls when implementing Modbus ASCII communications? 3. Can you provide examples where Modbus ASCII might be preferred over RTU? 4. In terms of performance, how do Modbus ASCII and RTU compare under similar conditions?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Csharp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值