引: 上位机需要与单片机通讯,一次性写入全部数据。这里简单介绍如何根据有数据地址、偏移量、长度、数值等信息生成下行报文的方法。寄存器长度为两个字节,储存模式为大端模式。
下行报文格式参见博客https://blog.csdn.net/Serendipitor/article/details/129745301?spm=1001.2014.3001.5501
简单模式:数据长度16位(Int16或UInt16)
此时,配置信息为Int16数据与寄存器地址。将Int16的数据值转换为相邻的两个字节,重复若干次,即可完成整个寄存器的报文。
private byte[] IntToBytes(int a)
{
int high = a / 256;
int low = a % 256;
string highstring = Convert.ToString((short)high,2).PadLeft(8,"0");
string lowstring = Convert.ToString((short)low,2).PadLeft(8,"0");
byte[] b = new byte[2];
b[0] = Convert.ToInt16(highstring,2);
b[1] = Convert.ToInt16(lowstring,2);
return b;
}
复杂模式:Addr的结构
Addr结构如下,有地址、偏移量、长度三个成员变量。
而待写入的数据就是一个Addr与int的字典Dictionary<Addr,int>。WriteReg()方法将这个字典解析为字节数组。
internal class Addr
{
public int _addr;
public int _offset;
public int _length;
public Addr(int addr, int offset, int length)
{
_addr = addr;
_offset = offset;
_length = length;
}
}
private byte[] WriteReg(Dictionary<Addr, int> Data,int len,int stpos)
{
byte[] b = new byte[len * 2];
foreach(Addr a in Data.Keys)
{
int addr = a._addr;
int length = a._length;
int offset = a._offset;
int value = Data[a];
string boolstring;
if (length == 16)
{
//一个寄存器16位
if (value >= 0)
{
boolstring = Convert.ToString(value, 2).PadLeft(16, '0');
}
else
{
boolstring = Convert.ToString((Int16)value, 2).PadLeft(16, '0');
}
b[(addr - stpos) * 2 + 0] = Convert.ToByte(boolstring.Substring(0, 8), 2);
b[(addr - stpos) * 2 + 1] = Convert.ToByte(boolstring.Substring(8, 8), 2);
}
else if (length == 8)
{
if (offset == 0)
{
b[(addr - stpos) * 2 + 1] = (byte)value;
}
else
//否则偏移量为8
{
b[(addr - stpos) * 2 + 0] = (byte)value;
}
}
else if (length == 1)
{
if (offset >= 0 && offset <= 7)
{
int temp = b[(addr - stpos) * 2 + 1];
temp |= value << offset;
b[(addr - stpos) * 2 + 1] = (byte)temp;
}
else
{
int temp = b[(addr - stpos) * 2 + 0];
temp |= value << offset;
b[(addr - stpos) * 2 + 0] = (byte)temp;
}
}
}
return b;
}
最后,把下行报文依照:从机号-指令-开始地址-寄存器数量-字节数-内容-CRC校验位的格式封起来
public byte[] Mod(int ID=0X81, char func='w', int addr=31000,int len=20, params byte[]a)
{
byte[] b = default;
//读指令
if (func == 'r')
{
//读指令固定长度为8字节
b = Enumerable.Repeat((byte)0,8).ToArray();
b[0] = (byte)ID;
b[1] = 0x03;
b[2] = b[2] = Convert.ToByte(addr/ 256);//开始地址高字节
b[3] = Convert.ToByte(addr % 256);//开始地址低字节
b[4] = Convert.ToByte(len / 256);//寄存器数量高字节
b[5] = Convert.ToByte(len % 256);//寄存器数量低字节
byte[] crc = ModbusCrcCalc(b, 6);
b[6] = crc[0];
b[7] = crc[1];
}
else
{
b = Enumerable.Repeat((byte)0, 9+len*2).ToArray();
b[0] = (byte)ID;
b[1] = 0x10;
b[2] = b[2] = Convert.ToByte(addr / 256);//开始地址高字节
b[3] = Convert.ToByte(addr % 256);//开始地址低字节
b[4] = Convert.ToByte(len / 256);//寄存器数量高字节
b[5] = Convert.ToByte(len % 256);//寄存器数量低字节
b[6] = Convert.ToByte(len *2);//寄存器数量低字节
Array.Copy(a, 0, b, 7, len * 2);
byte[] crc = ModbusCrcCalc(b, len * 2 + 7);
b[len*2+7] = crc[0];
b[len*2+8] = crc[1];
}
return b;
}
//计算校验位
private byte[] ModbusCrcCalc(Byte[] data, int lenth = 0)//计算CRC16校验码
{
if (lenth == 0)
lenth = data.Length;
// crc计算赋初始值
int crc = 0xffff;
for (int i = 0; i < lenth; i++)
{
crc = crc ^ data[i];
for (int j = 0; j < 8; j++)
{
int temp;
temp = crc & 1;
crc = crc >> 1;
crc = crc & 0x7fff;
if (temp == 1)
{
crc = crc ^ 0xa001;
}
crc = crc & 0xffff;
}
}