C#上位机基于Modbus读取仪器波形
大家好!我是付工。
最近在做一个仪器测量的上位机项目,涉及到采集仪器波形,跟大家分享一下其中的关键技术点。
项目背景
这个项目涉及到两款仪器,分别是青智C2和青智F2,其中C2是采用Modbus协议,F2是采用自定义协议,需要对实验过程中的波形数据进行采集并存储,便于后续查看报告。

本文主要针对青智C2仪表进行说明,青智C2是一个发电机专用的测试仪,通信协议是标准ModbusRTU协议。

协议说明
这个仪表的协议和我们平常看到的所有不同,正常的Modbus地址表,每个地址会对应唯一的参数。
这种仪表可以做很多种实验,每种实验的数据量比较大,Modbus保持型存储区的65536个寄存器不够用,因为仪表内部做了一个分页面的机制,不同的页面对应不同的实验数据,当我们需要读取数据时,首先切换对应的页面,再进行读取。



波形读取
然后我们来看波形数据是如何读取的。
一个完整波形应该包含如下几个元素:
1.曲线点数
2.X轴数据
3.Y(1)-Y(n)数据
因此每个波形的寄存器地址表设计如下:

我们来分析一个这个地址表:
- 002A对应曲线点数(N<=800)
- 002B对应X轴间距系数
- 002C-034B一共800个寄存器对应800个X轴数据
- 034C-098B一共1600个寄存器对应800个线路1突加电压曲线Y轴数据
- 098C-0FCB一共1600个寄存器对应800个线路2突加电压曲线Y轴数据
- 0FCC-160B一共1600个寄存器对应800个线路3突加电压曲线Y轴数据
- 读取后续6个波形Y轴数据
我们按照这个思路来进行编写代码:
第1步:切换界面至界面1
第2步:读取从002A开始的两个寄存器,第一个寄存器作为数量count,第二个寄存器的值作为X轴间距系数
第3步:读v取002C开始的count个寄存器作为X轴数据
第4步:读取034C开始的count*2个寄存器作为线路1电压数据
第5步:读取098C开始的count*2个寄存器作为线路2电压数据
第6步:读取0FCC开始的count*2个寄存器作为线路3电压数据
第7-12步:读取后续6个Y轴曲线
第13步:解锁界面
整个过程封装成一个方法,参考代码如下:
public override OperateResult<List<string>> GetTJChart(){
//【1】锁定页面1
var lockResult = this.LockPage(1);
if (!lockResult.IsSuccess)
{
return OperateResult.CreateFailResult<List<string>>(lockResult);
}
List<string> result = new List<string>();
//【2】读取数据点数和X轴系数
var readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x002A, 2);
if (!readResult.IsSuccess)
{
return OperateResult.CreateFailResult<List<string>>(readResult);
}
byte[] data = readResult.Content;
ushort[] data1 = UShortLib.GetUShortArrayFromByteArray(data, this.dataFormat);
result.AddRange(data1.Skip(0).Take(data1.Length).ToArray().Select(c => c.ToString()));
//确定点数并判断是否超过范围
ushort count = data1[0];
if (count > 800)
{
return OperateResult.CreateFailResult<List<string>>("读取数据过大:" + count);
}
//【3】读取X曲线
readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x002C, count);
if (!readResult.IsSuccess)
{
return OperateResult.CreateFailResult<List<string>>(readResult);
}
data = readResult.Content;
short[] data2 = ShortLib.GetShortArrayFromByteArray(data, this.dataFormat);
result.Add(string.Join("_", data2));
//【4】读取线路1Y曲线
readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x034C, (ushort)(count * 2));
if (!readResult.IsSuccess)
{
return OperateResult.CreateFailResult<List<string>>(readResult);
}
data = readResult.Content;
float[] data3 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
result.Add(string.Join("_", data3));
//【5】读取线路2Y曲线
readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x098C, (ushort)(count * 2));
if (!readResult.IsSuccess)
{
return OperateResult.CreateFailResult<List<string>>(readResult);
}
data = readResult.Content;
float[] data4 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
result.Add(string.Join("_", data4));
//【6】读取线路3Y曲线
readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x0FCC, (ushort)(count * 2));
if (!readResult.IsSuccess)
{
return OperateResult.CreateFailResult<List<string>>(readResult);
}
data = readResult.Content;
float[] data5 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
result.Add(string.Join("_", data5));
//【7】读取平均突加电压Y曲线
readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x160C, (ushort)(count * 2));
if (!readResult.IsSuccess)
{
return OperateResult.CreateFailResult<List<string>>(readResult);
}
data = readResult.Content;
float[] data6 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
result.Add(string.Join("_", data6));
//【8】读取突加频率Y曲线
readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x160C, (ushort)(count * 2));
if (!readResult.IsSuccess)
{
return OperateResult.CreateFailResult<List<string>>(readResult);
}
data = readResult.Content;
float[] data7 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
result.Add(string.Join("_", data7));
//【9】读取线路1突加电流Y曲线
readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x228C, (ushort)(count * 2));
if (!readResult.IsSuccess)
{
return OperateResult.CreateFailResult<List<string>>(readResult);
}
data = readResult.Content;
float[] data8 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
result.Add(string.Join("_", data8));
//【10】读取线路2突加电流Y曲线
readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x28CC, (ushort)(count * 2));
if (!readResult.IsSuccess)
{
return OperateResult.CreateFailResult<List<string>>(readResult);
}
data = readResult.Content;
float[] data9 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
result.Add(string.Join("_", data9));
//【11】读取线路3突加电流Y曲线
readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x2F0C, (ushort)(count * 2));
if (!readResult.IsSuccess)
{
return OperateResult.CreateFailResult<List<string>>(readResult);
}
data = readResult.Content;
float[] data10 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
result.Add(string.Join("_", data10));
//【12】读取平均突加电流Y曲线
readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x354C, (ushort)(count * 2));
if (!readResult.IsSuccess)
{
return OperateResult.CreateFailResult<List<string>>(readResult);
}
data = readResult.Content;
float[] data11 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
result.Add(string.Join("_", data11));
//【13】解锁页面
this.UnLockPage(0);
return OperateResult.CreateSuccessResult(result);
}
将读取的数据存储到数据库中,将每个Y轴作为一个字段,里面存储的若干个数据采用字符串拼接成一个字符串进行存储。

这样在测试报告中可以通过查询数据库,将数据通过波形图展示出来。

多寄存器读取
如果数据点数为800,数据类型为浮点数,那么每个Y轴需要读取1600个寄存器,这时候应该怎么去读呢?
Modbus一次性最多可以读取125个寄存器,需要分1600/125=13次去读。
对于这种不确定读取数量的,最好是在通信库底层去实现。
public OperateResult<byte[]> Read(ModbusArea area, byte devId, ushort startAddress, ushort length){
var resultBytes = new List<byte>();
int index = 0;
while (length > 0)
{
var maxToRead = Math.Min(length, (area==ModbusArea.保持寄存器||area==ModbusArea.输入寄存器)?this.MaxRegisterPduSize:this.MaxCoilPduSize);
var read = ReadWithSingleRequest(area, devId, (ushort)(startAddress + index), maxToRead);
if (!read.IsSuccess)
{
return OperateResult.CreateFailResult<byte[]>(read);
}
resultBytes.AddRange(read.Content);
length -= maxToRead;
index += maxToRead;
}
return OperateResult.CreateSuccessResult(resultBytes.ToArray());
}