# 概述

由于先前远程机器没有开机,不能进行调试硬件设备,无法进行灯光功能和警报器功能的开发调试,因此尝试使用本地串口设备模拟来进行协助相应开发调试。

# 报警器控制部分

根据报警器输入输出设备的硬件示意图和相关资料可以得知:

  1. 其输入输出模块使用了 Modbus 协议,使用读写寄存器来控制对应功能的信号;
  2. 该模块的输入端有三路接入了硬件设备,分别为红灯、绿灯、报警器;
  3. 该模块使用 USB 串口设备接入工控机,所以能通过设备管理器找出对应的串口号;
  4. 该模块的输出端输出一个信号

# 接入虚拟串口设备

根据以上信息,目前首先使用 Vitual Serial Port Driver 来创建虚拟串口连接

image-20221120154702152

其中 COM2-COM3 COM4-COM5 COM6-COM7 分别为三组虚拟串口组用于模拟设备连接,分别对应两个灯光模组,一个报警器的输入输出模组.

# 虚拟 Modbus 从机设备

使用 Modbus Slave 来创建虚拟 Modbus 从设备,来模拟输入输出模块

image-20221120154831034

# 寄存器定义

经过多次测试得知,该模块的寄存器如下:

保持寄存器 4 个,其地址定义为:

0: 绿灯; 1: 红灯; 2: 报警器;

对应的值为 1 时,对应的设备激活.

image-20221120155911642

输入寄存器 1 个,其地址定义为:

image-20221120160401176

0: 信号输入 (当换卷时会出现输入信号,值变为 1, 正常为 0)

代码实现部分使用 ModbusSerialMaster 模块来管理 Modubus 设备.

1
ModbusSerialMaster master;

报警器功能实现,具体逻辑不在此赘述.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//绿灯 0  红灯 1  警报 2

//报警器红灯,且鸣笛
console("Alarm start !", ConsoleColor.Red);

//master.WriteMultipleRegistersAsync(1, 0000, registerBuffer);//无效!
//报警器信号发送
master.WriteSingleRegister(1, 0000, 0);
master.WriteSingleRegister(1, 0001, 1);
master.WriteSingleRegister(1, 0002, 1);

//持续报警
Thread.Sleep(Duration);

//报警器关闭,绿灯开启
master.WriteSingleRegister(1, 0000, 1);
master.WriteSingleRegister(1, 0001, 0);
master.WriteSingleRegister(1, 0002, 0);
//master.WriteMultipleRegistersAsync(1, 0000, registerBuffer);//无效!
console("Alarm end !", ConsoleColor.Red);

经过测试发现 master.WriteMultipleRegistersAsync () 无效,只能使用 master.WriteSingleRegister () 逐个修改保持寄存器的值.

# 连接测试

在配置文件中将 AlarmPortName 修改到对应的串口 COM3,Modbus Slave 使用 COM2

image-20221120163941370

启动 DebugConsole, 注意到输出串口打开成功

image-20221120164047483

虚拟串口中可以看出,COM2 和 COM3 设备已经完成连接

image-20221120164029152

Modbus 保持寄存器修改

image-20221120164258526

当检测到瑕疵时,对应的寄存器值也被修改

image-20221120164413218

手动修改输入寄存器值

image-20221120164512014

成功触发换卷事件

image-20221120164450561

image-20221120164544684

# 灯光控制部分

# 协议封装

灯光控制模块的通信协议如图

image-20221120163300477

根据协议进行封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public void sendCmd(byte command, byte port, int value) //光源控制器命令发送
{
byte[] senBuffer = new byte[8];//发送缓冲
byte[] b_value = new byte[3]; //值用3位字节表示
byte[] b_check = new byte[2]; //校验位用2位字表示
string str1 = value.ToString("X3"); //亮度值处理,将value转化为3位的16进制字符
b_value[0] = Convert.ToByte(str1[0]); //将数据转化为Ascall码十进制数
b_value[1] = Convert.ToByte(str1[1]); //将数据转化为Ascall码十进制数
b_value[2] = Convert.ToByte(str1[2]); //将数据转化为Ascall码十进制数

senBuffer[0] = 0x24; //特征字$
//指令字
senBuffer[1] = Convert.ToByte(command + 48); //命令1表示打开,2表示关闭,3表示设置,4表示读取,command+48表示将数据转化为Ascall码十进制数
//通道字
senBuffer[2] = Convert.ToByte(port + 48); //通道,port+48表示将数据转化为Ascall码十进制数
senBuffer[3] = b_value[0]; //数据1
senBuffer[4] = b_value[1]; //数据2
senBuffer[5] = b_value[2]; //数据3

Check_Dell(ref senBuffer); //调用验证位处理函数,处理senBuffer[]最后2个验证位

if (!serialPort.IsOpen)
serialPort.Open();
if (busy)
return;
busy = true;
Console.WriteLine($"port {serialPort.PortName} send cmd:{Encoding.ASCII.GetString(senBuffer)}");
//serialPort.Write(senBuffer, 0, 8); //发送指令
serialPort.Write(Encoding.ASCII.GetString(senBuffer));
serialPort.Close();
busy = false;

}

void Check_Dell(ref byte[] buffer) //验证位处理函数
{
buffer[buffer.Length - 2] = 0; //任何数据与0异或都等于其本身,故先对第一个(高位)验证位赋0
for (int i = 0; i < buffer.Length - 2; i++)
{
buffer[buffer.Length - 2] = Convert.ToByte(buffer[buffer.Length - 2] ^ buffer[i]); //验证位分别与前面的每个数据累计作异或运算
}
buffer[buffer.Length - 1] = buffer[buffer.Length - 2]; //令buffer后2位元素(即验证位)相等
buffer[buffer.Length - 2] >>= 4; //高位右移4位,得到高位验证位
buffer[buffer.Length - 1] &= 15; //15二进制即0000 1111,按位与可获得低位验证位
buffer[buffer.Length - 2] = Convert.ToByte(buffer[buffer.Length - 2].ToString("X1")[0]); //强制转化16为进制字符,再转化为Ascall码
buffer[buffer.Length - 1] = Convert.ToByte(buffer[buffer.Length - 1].ToString("X1")[0]);
}

# 连接测试

使用串口调试器打开串口 COM5, 设置灯光控制模块串口为 COM4

image-20221120170723523

测试串口发送消息

1
lightA.Intensity(1,200);

在串口调试器中成功收到串口消息

image-20221120170505133