请教Modbus高手!我想根据Modbus03功能码代码修改出01功能码代码,求高手给个思路,感激不尽!!(串口/Modbus~RTU)

OROCHIORI03 2009-09-24 02:40:35
虽然是用C#语言编写的 但是懂Modbus的高手应该能看懂!!
这是一个将两个串口连接起来相互通信的一个程序(例如用串口工具把COM2和COM3连接起来。然后该程序连接的是COM2,设备连接的是COM3,

那么该程序就可以读取到设备中寄存器的值)。
下面的代码是实现功能码03的一个完整例子,已经调试成功!我把我能理解的东西已经写上注释,不理解的地方也写上了注释求高手帮忙分析。我的目标是通过03功能的实现,来修改出01功能。(03是读取寄存器中值是二进制值。01读取线圈当前状态值【0或1】)

我在后面把Modbus协议中关于03和01功能码及CRC校验的说明贴出来。

主界面Timer事件:
C# code
using System.IO.Ports;
modbus mb = new modbus();//modbus类的代码在后面给出
private SerialPort sp = new SerialPort();//声明串行端口sp
private void timer1_Tick(object sender, EventArgs e)
{
this.listview1.Items.Clear();

short[] values = new short[Convert.ToInt32(txtRegisterQty.Text)];//txtRegisterQty是用来输入寄存器数量的TextBox
ushort pollStart;//表示起始地址的变量
ushort pollLength;//表示寄存器数量的变量

if (txtStartAddr.Text != "")
pollStart = Convert.ToUInt16(txtStartAddr.Text);//txtStartAddr是用来输入寄存器的起始地址的TextBox(例如:如果写0,listview显示的第一项是设备中的第一个寄存器。如果写10,listview的第一项是设备中的第九个寄存器)
else
pollStart = 0;
if (txtRegisterQty.Text != "")
pollLength = Convert.ToUInt16pollLength =Convert.ToUInt16(txtRegisterQty.Text);//txtRegisterQty是用来输入寄存器数量的TextBox(例如:如果写1,listview1中就会只显示设备中的第一个寄存器,如果写10,listview1就会显示10个寄存器)
else
pollLength = 20;

while (mb!SendFc3(Convert.ToByte(txtSlaveID.Text), pollStart, pollLength, ref values));//返回values,下面的for循环就是将返回的values加载到listview中(SendFc3方法内容详见后面的SendFc3方法代码)。

for (int i =0; i < pollLength; i++)
{
listview1.Items.Add(new ListViewItem(new string[] { Convert.ToString(pollStart+i),Convert.ToString(pollStart + i + 40001), values[i].ToString()}));//将三条信息加载到listview1的三个列中,前两条信息是固定的,values[i]的值是从上面的SendFc3方法返回的。
}
}



下面的四个方法全部写在modbus类中。
C# code
using System.IO.Ports;
private SerialPort sp = new SerialPort();//声明串行端口sp
SendFc3方法:
public bool SendFc3(byte address, ushort start, ushort registers, ref short[] values)
{
if (sp.IsOpen)//当串口是打开状态时
{

byte[] message = new byte[8];
byte[] response = new byte[5 + 2 * registers];//寄存器数量*2 +5的目的是什么?

BuildMessage(address, (byte)3, start, registers, ref message);//因为要用的是Modbus的03功能码去读,所以第二个参数是3。如果我想把03功能改成01功能,首先能肯定必须要改的就是这个方法中的第二个参数,既将3该写成1。(BuildMessage方法内容详见后面代码后面BuildMessage方法代码)。

sp.Write(message, 0, message.Length);//BuildMessage方法返回来的message写入到串行端口
GetResponse(ref response);//从串口把设备中寄存器的值读出来了(GetResponse方法内容详见后面的GetResponse方法代码)。

for (int i = 0; i < (response.Length - 5) / 2; i++)//上面是将寄存器的数量*2加5这回-5/2还原了能理解,就是不明白上面为什么*2加5。
{
//我能猜出下面这三行是把读出来的值进行处理,但不明白为什么这样处理。我感觉想要实现01功能码功能,这地方是关键!
values[i] = response[2 * i + 3];//不明白
values[i] <<= 8;//不明白
values[i] += response[2 * i + 4];//不明白
}
return true;
}
else
{
return false;
}
}

//BuildMessage方法:这个方法中message数组中的[0]~[7]的值是对应固定类型信息的(协议里的规定)。例如[0]代表地址、[1]代表功能。
private void BuildMessage(byte address, byte type, ushort start, ushort registers, ref byte[] message)
{
byte[] CRC = new byte[2];//声明两个字节的CRC数组

message[0] = address;//设备地址为address
message[1] = type;// 这条消息是要读取什么功能码,这里传进来的是03功能码。这是实现01功能还是03功能区别的地方
message[2] = (byte)(start >> 8);//起始寄存器地址高8位(不理解start >> 8的意思)
message[3] = (byte)start;//起始寄存器地址低8位
message[4] = (byte)(registers >> 8);//读取的寄存器数高8位(不理解registers >> 8的意思)
message[5] = (byte) registers;//读取的寄存器数低8位

GetCRC(message, ref CRC);//GetCRC方法内容详见后面的GetCRC方法代码
message[message.Length-2] = CRC[0];//CRC校验的低8位
message[message.Length -1] = CRC[1];//CRC校验的高8位
}


//GetCRC方法:在Modbus协议中这个函数叫CRC校验。我觉得这个方法的作用是把相对应的寄存器的值转化成CRC高位和低位,我在程序画面看到的值就是CRC高位+CRC低位的值,不知道理解的对不对!(CRC校验的含义详见后面的【生成CRC-16校验字节的步骤】)
private void GetCRC(byte[] message, ref byte[] CRC)
{
ushort CRCFull = 0xFFFF;
byte CRCHigh = 0xFF;
byte CRCLow = 0xFF;
char CRCLSB;
for (int i = 0; i < (message.Length) - 2; i++)
{
CRCFull = (ushort)(CRCFull ^ message[i]);

for (int j = 0; j < 8; j++)
{
CRCLSB = (char)(CRCFull & 0x0001);
CRCFull = (ushort)((CRCFull >> 1) & 0x7FFF);

if(CRCLSB ==1)
CRCFull =(ushort)(CRCFull^0xA001);
}
}
CRC[1] =CRCHigh=(byte)((CRCFull >> 8) & 0xFF);
CRC[0] = CRCLow = (byte)(CRCFull & 0xFF);
}

//GetResponse方法:
private void GetResponse(ref byte[] response)
{
for (int i = 0; i < response.Length; i++)
{
response[i] = (byte)(sp.ReadByte());//从串口中把寄存器的值读出来
}
}

______________________________________________________________________________________________________________

【重要问题】要实现01功能,我猜想可能要要修改下CRC校验的那个方法。还有可能不用去修改CRC校验,而是去修改SendFc3方法的那个for循环。请高手指教,最好能写出代码,感激不尽!!!
PS:我用ModSim32这个软件来模拟设备的,在ModSim32界面中选择03功能,本程序可以正常运行实现读取寄存器值。
我的目的是想做到在ModSim32中选择01这个功能的时候,程序可以读取线圈状态值,实现对线圈状态值的读取(值是0或1)。
______________________________________________________________________________________________________________

Modbus协议中对01和03功能码及CRC校验的分析
【01功能码分析】
01功能码:读可读写数字量寄存器(取得一组逻辑线圈的当前状态(ON/OFF)):
计算机发送命令:[设备地址] [命令号01] [起始寄存器地址高8位] [低8位] [读取的寄存器数高8位] [低8位] [CRC校验的低8位] [CRC校验的

高8位]
例:[11][01][00][13][00][25][CRC低][CRC高]
意义如下:
<1>设备地址:在一个485总线上可以挂接多个设备,此处的设备地址表示想和哪一个设备通讯。例子中为想和17号(十进制的17是十六进制的11)通讯。
<2>命令号01:读取数字量的命令号固定为01。
<3>起始地址高8位、低8位:表示想读取的开关量的起始地址(起始地址为0)。比如例子中的起始地址为19。
<4>寄存器数高8位、低8位:表示从起始地址开始读多少个开关量。例子中为37个开关量。
<5>CRC校验:是从开头一直校验到此之前。在此协议的最后再作介绍。此处需要注意,CRC校验在命令中的高低字节的顺序和其他的相反。
设备响应:[设备地址] [命令号01] [返回的字节个数][数据1][数据2]...[数据n][CRC校验的低8位] [CRC校验的高8位]
例:[11][01][05][CD][6B][B2][0E][1B][CRC低][CRC高]
意义如下:
<1>设备地址和命令号和上面的相同。
<2>返回的字节个数:表示数据的字节个数,也就是数据1,2...n中的n的值。
<3>数据1...n:由于每一个数据是一个8位的数,所以每一个数据表示8个开关量的值,每一位为0表示对应的开关断开,为1表示闭合。比如例子中,表示20号(索引号为19)开关闭合,21号断开,22闭合,23闭合,24断开,25断开,26闭合,27闭合...如果询问的开关量不是8的整倍数,那么最后一个字节的高位部分无意义,置为0。
<4>CRC校验同上。

【03功能码分析】
03功能码:读可读写模拟量寄存器(在一个或多个保持寄存器中取得当前的二进制值):
计算机发送命令:[设备地址] [命令号03] [起始寄存器地址高8位] [低8位] [读取的寄存器数高8位] [低8位] [CRC校验的低8位] [CRC校验的高8位]
例:[11][03][00][6B][00][03][CRC低][CRC高]
意义如下:
<1>设备地址和上面的相同。
<2>命令号:读模拟量的命令号固定为03。
<3>起始地址高8位、低8位:表示想读取的模拟量的起始地址(起始地址为0)。比如例子中的起始地址为107。
<4>寄存器数高8位、低8位:表示从起始地址开始读多少个模拟量。例子中为3个模拟量。注意,在返回的信息中一个模拟量需要返回两个字节。
设备响应:[设备地址] [命令号03] [返回的字节个数][数据1][数据2]...[数据n][CRC校验的低8位] [CRC校验的高8位]
例:[11][03][06][02][2B][00][00][00][64][CRC低][CRC高]
意义如下:
<1>设备地址和命令号和上面的相同。
<2>返回的字节个数:表示数据的字节个数,也就是数据1,2...n中的n的值。例子中返回了3个模拟量的数据,因为一个模拟量需要2个字节所以共6个字节。
<3>数据1...n:其中[数据1][数据2]分别是第1个模拟量的高8位和低8位,[数据3][数据4]是第2个模拟量的高8位和低8位,以此类推。例子中返回的值分别是555,0,100。
<4>CRC校验同上。

【生成CRC-16校验字节的步骤】
①装如一个16位寄存器,所有数位均为1。
②该16位寄存器的高位字节与开始8位字节进行“异或”运算。运算结果放入这个16位寄存器。
③把这个16寄存器向右移一位。
④若向右(标记位)移出的数位是1,则生成多项式1010000000000001和这个寄存器进行“异或”
运算;若向右移出的数位是0,则返回③。
⑤重复③和④,直至移出8位。
⑥另外8位与该十六位寄存器进行“异或”运算。
⑦重复③~⑥,直至该报文所有字节均与16位寄存器进行“异或”运算,并移位8次。
⑧这个16位寄存器的内容即2字节CRC错误校验,被加到报文的最高有效位。
...全文
1443 4 打赏 收藏 转发到动态 举报
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
mmzhang1986 2012-02-17
  • 打赏
  • 举报
回复
你直接把03改成01就可以了,然后用这个函数得出message然后sp.write
BuildMessage(byte address, byte type, ushort start, ushort registers, ref byte[] message)
yrf12345 2010-05-17
  • 打赏
  • 举报
回复
高手,有没有源码。传一个
letian910 2010-04-10
  • 打赏
  • 举报
回复
modbus mb = new modbus();//modbus类的代码在后面给出
modbus类的代码呢?
schlafenhamster 2009-09-25
  • 打赏
  • 举报
回复
从机对03的应答是:
01 03 02 dh dl 所以有5个字节。是头后跟 xx xx 检验码。

27,375

社区成员

发帖
与我相关
我的任务
社区描述
硬件/嵌入开发 单片机/工控
社区管理员
  • 单片机/工控社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧