Go to my github

Modbus通讯协议学习 - 认识篇

什么是Modbus?

 

Modbus 协议是应用于电子控制器上的一种通用语言。通过此协议,控制器相互之间、控制器经由网络(例如以太网)和其它设备之间可以通信。Modbus 协议定义了一个控制器能认识使用的消息结构,而不管它们是经过何种网络进行通信的。它描述了一控制器请求访问其它设备的过程,如果回应来自其它设备的请求,以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式

Modbus 是一个请求/应答协议

Modbus

以下是要分解的Modbus热图

 image

 

Modbus消息帧

 

了解了它,会使你对串口通信有一个清晰的认识!

image

通用消息帧

image

 

ASCII消息帧 (在消息中的每个8Bit 字节都作为两个ASCII字符发送)

十六进制,ASCII字符0...9,A...F

消息中的每个ASCII字符都是一个十六进制字符组成

每个字节的位

1个起始位

n个数据位,最小的有效位先发送

1个奇偶校验位,无校验则无

1个停止位(有校验时),2个Bit(无校验时)

错误检测域

LRC(纵向冗长检测)

ASCII消息帧

位顺序(ASCII)

RTU消息帧

8位二进制,十六进制数0...9,A...F

消息中的每个8位域都是一个两个十六进制字符组成

每个字节的位

1个起始位

8个数据位,最小的有效位先发送

1个奇偶校验位,无校验则无

1个停止位(有校验时),2个Bit(无校验时)

错误检测域

CRC(循环冗长检测)

RTU消息帧

位顺序(RTU)

 

CRC校验 高低位 (http://baike.baidu.com/view/1664507.htm)

ModBus协议中,CRC校验码计算方法为:

1.预置1个16位的寄存器为十六进制FFFF(即全为1);称此寄存器为CRC寄存器;
2.把第一个8位二进制数据(既通讯信息帧的第一个字节)与16位的CRC寄存器的低8位相异或,把结果放于CRC寄存器;
3.把CRC寄存器的内容右移一位(朝低位)用0填补最高位,并检查右移后的移出位;
4.如果移出位为0:重复第3步(再次右移一位); 如果移出位为1:CRC寄存器与多项式A001(1010 0000 0000 0001)进行异或;
5.重复步骤3和4,直到右移8次,这样整个8位数据全部进行了处理;
6.重复步骤2到步骤5,进行通讯信息帧下一个字节的处理;
7.将该通讯信息帧所有字节按上述步骤计算完成后,得到的16位CRC寄存器的高、低字节进行交换;
8.最后得到的CRC寄存器内容即为:CRC码。
原文链接:https://blog.csdn.net/chenchenxgnz/article/details/51148940

public static string CRCCheck(string val)
        {
            val = val.TrimEnd(' ');
            string[] spva = val.Split(' ');
            byte[] bufData = new byte[spva.Length + 2];
            bufData = ToBytesCRC(val);
            ushort CRC = 0xffff;
            ushort POLYNOMIAL = 0xa001;
            for (int i = 0; i < bufData.Length - 2; i++)
            {
                CRC ^= bufData[i];
                for (int j = 0; j < 8; j++)
                {
                    if ((CRC & 0x0001) != 0)
                    {
                        CRC >>= 1;
                        CRC ^= POLYNOMIAL;
                    }
                    else
                    {
                        CRC >>= 1;
                    }
                }
            }
            return Maticsoft.DBUtility.HLConvert.ToHex(System.BitConverter.GetBytes(CRC));
        }
        /// <summary>
        /// 例如把如下字符串转换成字节数组
        /// AA AA AA AA 0A 00 68 00 06 03 04 54 21 28 22 E5 F3 16 BB BB BB BB   转换为字节数组
        /// </summary>
        /// <param name="hex">十六进制字符串</param>
        /// <returns></returns>
        public static byte[] ToBytesCRC(string hex)
        {
            string[] temp = hex.Split(' ');
            byte[] b = new byte[temp.Length + 2];

            for (int i = 0; i < temp.Length; i++)
            {
                b[i] = Convert.ToByte(temp[i], 16);
            }

            return b;
        }
        /// <summary>
        /// 将字节数据转换为十六进制字符串,中间用 “ ”分割 如:AA AA AA AA 0A 00 68 00 06 03 04 54 21 28 22 E5 F3 16 BB BB BB BB
        /// </summary>
        /// <param name="vars">要转换的字节数组</param>
        /// <returns></returns>
        public static String ToHex(byte[] vars)
        {
            return BitConverter.ToString(vars).Replace('-', ' ').Trim();
        }

CS校验(累加和)

public static string CSCheck(string str)
        {
            if (str.Length == 0) return "";
            else str = str.Trim();
            byte[] sss = ToBytes(str);
            int n = 0;
            for (int i = 0; i < sss.Length; i++)
            {
                n += sss[i];
            }
            return ToHex(n);
        }
        /// <summary>
        /// AB CD 12 3B     转换为字节数组
        /// </summary>
        /// <param name="hex">十六进制字符串</param>
        /// <returns></returns>
        public static byte[] ToBytes(string hex)
        {
            string[] temp = hex.Split(' ');
            byte[] b = new byte[temp.Length];

            for (int i = 0; i < temp.Length; i++)
            {
                if (temp[i].Length > 0)
                    b[i] = Convert.ToByte(temp[i], 16);
            }

            return b;
        }
        /// <summary>
        /// 转换为符合本程序的十六进制格式
        /// </summary>
        /// <param name="var">1 2 3 等。</param>
        /// <returns>返回十六进制字符串,如果是1-9的话,前面带零</returns>
        /// <example>例如: 5  ="05"  12 ="0C" 无论何时,都是两位数。  </example>
        public static string ToHex(int var)
        {
            int cs = var;
            string tmp = "";
            if (cs == 0) { tmp = "00"; }
            while (cs > 0)
            {
                int ys;
                cs = Math.DivRem(cs, 256, out ys);
                tmp = tmp.Insert(0, string.Format(" {0}", Right("00" + Convert.ToString(ys, 16), 2).ToUpper()));
            }
            return tmp.Trim();
        }
        public static string Right(string str, int Length)
        {
            if ((Length <= 0) || (str == null))
            {
                return "";
            }
            int length = str.Length;
            if (Length >= length)
            {
                return str;
            }
            return str.Substring(length - Length, Length);
        }

LRC校验(LRC错误校验用于ASCII模式)

/// <summary>
        /// 取模FF(255)
        /// 取反+1
        /// </summary>
        /// <param name="writeUncheck"></param>
        /// <returns></returns>
        public static string LRCheck(string writeUncheck)
        {
            char[] hexArray = new char[writeUncheck.Length];
            hexArray = writeUncheck.ToCharArray();
            int decNum = 0, decNumMSB = 0, decNumLSB = 0;
            int decByte, decByteTotal = 0;

            bool msb = true;

            for (int t = 0; t <= hexArray.GetUpperBound(0); t++)
            {
                if ((hexArray[t] >= 48) && (hexArray[t] <= 57))

                    decNum = (hexArray[t] - 48);

                else if ((hexArray[t] >= 65) & (hexArray[t] <= 70))
                    decNum = 10 + (hexArray[t] - 65);

                if (msb)
                {
                    decNumMSB = decNum * 16;
                    msb = false;
                }
                else
                {
                    decNumLSB = decNum;
                    msb = true;
                }
                if (msb)
                {
                    decByte = decNumMSB + decNumLSB;
                    decByteTotal += decByte;
                }
            }

            decByteTotal = (255 - decByteTotal) + 1;
            decByteTotal = decByteTotal & 255;

            int a, b = 0;

            string hexByte = "", hexTotal = "";
            double i;

            for (i = 0; decByteTotal > 0; i++)
            {
                b = Convert.ToInt32(System.Math.Pow(16.0, i));
                a = decByteTotal % 16;
                decByteTotal /= 16;
                if (a <= 9)
                    hexByte = a.ToString();
                else
                {
                    switch (a)
                    {
                        case 10:
                            hexByte = "A";
                            break;
                        case 11:
                            hexByte = "B";
                            break;
                        case 12:
                            hexByte = "C";
                            break;
                        case 13:
                            hexByte = "D";
                            break;
                        case 14:
                            hexByte = "E";
                            break;
                        case 15:
                            hexByte = "F";
                            break;
                    }
                }
                hexTotal = String.Concat(hexByte, hexTotal);
            }
            return hexTotal;
        }

        public void LRCheck(byte[] code)
        {
            int sum = 0;
            foreach (byte b in code)
            {
                sum += b;
            }
            sum = sum % 255;//取模FF(255)
            sum = ~sum + 1;//取反+1
            string lrc = Convert.ToString(sum, 16);
            return lrc;
        }

 

 

Modbus通讯协议中的四种字节顺序

国内设备基本上是A B C D顺序,国外设备基本上是B A D C顺序。低位优先字节交换。使用两个寄存器。
使用IEEE 754规范,如显示不正常可进行 字节顺序 交换位置即可。如下:

Float Big-endian 字节顺序:A B C D
Float Little-endian 字节顺序:D C B A
Float Big-endian byte swap 字节顺序:B A D C
Float Little-endian byte swap 字节顺序:C D A B

举个栗子:41 8C E8 EE
字节顺序: A B C D

 

自定义Modbus数据表

 

自定义Modbus数据表例子:

设备相关读取信息:

image

命令报文信息解析:

image

自定义Modbus数据表定义注意

 

串口调试工具

 

串口调试工具的使用.

image

 

串口调试工具 + RS485  就可以读取硬件上的数据,和向硬件请求了,如何使用请看“调试篇”会有详细的说明。

image image

网络调试助手:

       调试助手主要还是TCP协议通讯的一个调试工具

 image

资料:https://blog.csdn.net/chenchenxgnz/article/details/51148940?utm_term=modbus%E5%9C%B0%E5%9D%80%E9%AB%98%E5%AD%97%E8%8A%82%E5%92%8C%E4%BD%8E%E5%AD%97%E8%8A%82&utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~sobaiduweb~default-0-51148940&spm=3001.4430

 

 https://blog.csdn.net/dietu1085/article/details/101509125?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-10.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-10.no_search_link

 

    • Modbus poll 和Modbus slave是一组Modbus仿真软件,可以实现Modbus RTU、TCP、串口仿真等。
    • 仿真软件网址:https://modbustools.com/download.html
    • 在ModbusTCP中,Modbus poll 作为客户端请求数据,Modbus slave 作为服务器端处理请求。
    • 使用c语言编写客户端连接Modbus slave时,注意数据格式,一条指令一次性发出,否则连接会出错。
    • 使用软件时,需要指定功能码,在setup->slave definition或者poll definition中进行设置。
      – slave ID:从站编号(事务标识符)
      – function:功能码,0x01对应线圈操作,0x02对应离散量操作,0x03对应保持寄存器操作,0x04对应输入寄存器操作
      – address:开始地址
      – quantity:寄存器/线圈/离散量 的数量
posted @ 2013-06-14 13:36  峡谷少爷  阅读(170999)  评论(4编辑  收藏  举报