当前位置:首页 >> 计算机软件及应用 >>

多个串口设备数据的连续采集


工控程序设计

学习情景2.4 多个串口设备数据的连续采集

学习情景2.4 多个串口设备数据的连续采集
2.4.1 学习要点
1.知识点: 煤矿钻机性能检测设备的数据通信协议,数据帧的校验方法 2.技能点: 校验码的计算,上位机主动模式串口通信类的设计,HC-100智能测控仪、 JW-2A扭矩仪、LU-902M位式调节仪数据采集 2.4.2 任务描述 在前一个情景中实现了对单个HSDZC电能综合测试数据的接收。在钻 机性能测试系统中,还需要使用JW-2A扭矩仪、HC-100智能测控仪、LU902M位式调节仪来采集其它参数的值,而这些设备的数据通信规则不同, 所以必须对每种设备编写通信程序,并同步、连续地采集多个设备的数 据。 该教学情景中先针对上位机主动通信模式设计基类 CActiveCOMHelper,再分别对三种不同的仪器分别设计派生类来进行数 据管理,实现对多个不同设备进行同步、连续的数据采集。
工控程序设计
3

2.4.3 相关知识 1 煤矿钻机性能检测设备的数据通信协议 (1) HC-100智能测控仪 HC-100智能测控仪可以使用RS232、RS485接口与计算机通讯,数据格式 为1个起始位,8个数据位,无奇偶校验,1个停止位,共11位。设备 的所有数字变换成ASCII码进行传输:例如:仪表编号Addr=01,测量 值=123.4,则数据千位为31H、百位为32H、十位为33H、小数点为2EH ,个位为34H,符号位2BH为正,2DH为负。 从仪表读数据的指令格式为:EOT+仪表地址编号+52H+参数+ENQ 表2.4.1 发送给HC100的数据格式 EOT 04H 仪表编号(十位) 仪表编号(个位) R 30H 31H 参数 ENQ 05H

52H 30H 30H

工控程序设计
4

仪表返回数据的格式为: STX+ 仪表地址编号 + 符号位 + 测量值 + 校验码 +ETX。其中的数据参数值为符号位+4位有效位+小数点,共6位,校验 码为前面发送的9个字节之和。

表2.4.2 HC100返回的数据格式 STX 02H 仪表编号 30H 31H 符号 2bH 千位 百位 十位 小数点 个位 校验码 31H 32H 33H 2eH 34H 83H ETX 03H

工控程序设计
5

HC-100智能控制仪的数据处理流程如图2.4.1所示
清理缓存 开始

发送指令

接收数据 判断开始位、结束位是否有效 Y 判断符号位是否正确 Y 判断地址位是否正确 Y 提取、分析数据 N N

N

结束

图2.4.1 HC-100智能控制仪数据处理流程
工控程序设计
6

(2)JW-2A扭矩仪 JW-2A扭矩仪采用多机通讯方式,本机地址为0AH。数据位8位, 停止位1位。波特率可用跳线选择4800bps或2400bps。奇偶位在这里 做地址和数据判别位,1为地址,0为数据。仪器只被地址触发,即上 位机只有向仪器发0AH,并且奇偶位为1时,才能得到仪器的响应。仪 器被本机地址触发后,向上位机传回本机地址,然后等待上位机发来 的命令。受到命令后,根据命令执行不同的任务。命令为一个字节。 目前有效的命令有15个,下面叙述各命令代表的任务。 0:读扭矩转速 仪器受到命令后向上位机依次传送扭矩,转速,采 样时间3个参数。扭矩、转速为浮点数,每个参数4个字节。采样时间 为整数,2个字节。每个参数的传送顺序都是从内存的低地址到高地 址 1:存储操作。在测量状态收到此命令,将启动存储操作;在存储状 态收到此命令,将退出存储状态。 2:读仪器存储的数据数组。在非存储状态收到此命令,将向上位机 传送春初数据的数组。 3:释放操作。在测量状态收到此命令,将启动释放操作;在释放状 态仪器不再接受命令。
工控程序设计
7

4:扣除/不扣除转换。在扣除状态收到此命令,将退出扣除状态;在非扣除状 态收到此命令,将进入扣除状态。 5:内/外转速显示转换。在显示内转速时收到此命令,将显示外转速;在显示 外转速时收到此命令,将显示内转速。 6:调零操作。仪器才测量状态收到此命令,将启动调零操作。 7:读仪器状态。仪器收到命令后向上位机 传送仪器状态字节。状态字节各位 的定义如表2.4.3所示。 表2.4.3 JW-2A通信协议
Bit位臵 bit0 bit1 等于1的情况 存储状态 释放状态 等于0的情况 非存储状态 非释放状态

bit2
bit3

扣除状态
外转速状态

非扣除状态
内转速状态

bit4
bit5 bit6 bit7

调零状态
外转速通道为小电机转速 外转速通道有信号 仪器处于设臵状态

非调零状态
外转速通道为外转速 外转速通道无信号 仪器处于测量状态

工控程序设计
8

8:读模拟通道数据。仪器收到命令后向上位机依次传送0至7通道的A/D采样结 果。每通道2字节,高位在前。低14位表示数值大小,最高2位表示小数点位 臵。小数点位臵为0,有0位小数点;小数点位臵为1,有1位小数点;小数点 位臵为2,有2位小数点。 9:读0点。仪器收到命令后向上位机依次传送10个零点值和其相应的转速值。 每个零点6个字节(前4个字节是零点,浮点数,后2个字节是转速,无符号 整数,高位在前),共60个字节。 10:写零点。仪器受到命令后准备接受10个零点,共60个字节的数据。上位机 应按上述读零点的顺序依次将60个字节写回去。注意,写零点不能少于60字 节。 11:读扭矩参数。仪器收到命令后向上位机依次传送11个参数,共24个字节。 这11个参数的排列顺序,数据类型,数据长度如表2.4.4所示。 12:写扭矩参数。仪器收到命令后准备接受11个参数,共24个字节的数据。上 位机应按上述读扭矩参数的顺序依次将24个字节写回去。注意:写扭矩参数 不能少于24字节。 13:读模拟通道量程。仪器收到命令后向上位机依次传送8个通道的量程值。每 个量程2个字节,共16个字节。数据类型是无符号整数,高位在前。低14位 表示数值大小,最高2位表示小数点位臵。小数点位臵为0,有0位小数点; 小数点位臵为1,有1位小数点,小数点位臵为2,有2位小数点。
工控程序设计
9

表2.4.4
数据名称
系数 量程 齿数

JW-2A的扭矩参数
数据类型
无符号整数 浮点数 无符号整数

数据长度(byte)
2 4 2

采样周期
扭矩报警值 转速报警值 小电机转速 定时打印时间 转速模拟输出范围 标定温度 使用温度

无符号整数
浮点数 无符号整数 无符号整数 无符号整数 无符号整数 无符号整数 无符号整数

2
4 2 2 2 2 1 1

14:写模拟通道量程。仪器收到命令后准备接收8个通道的量程表。每个量程2个字 节,共16个字节。数据类型是无符号整数,高位在前。低14位表示数值大小,最高 2位表示小数点位臵。小数点位臵为0,有0位小数点;小数点位臵为1,有1位小数 点,小数点位臵为2,有2位小数点。上位机应按上述读模拟通道量程的顺序依次将 16个字节写回去。需要注意的是:写模拟通道量程不能少于16字节。
工控程序设计
10

JW-2A扭矩仪的数据采用IEEE浮点数形式表示,需要作浮点数字 节数组到浮点数的转换。如果用DCBA表示浮点数,D为高位,A为低位 ,每个字母表示一个字节,那么DCBA依次为 SXXX XXXX XMMM MMMM MMMM MMMM MMMM MMMM,其中S代表符号,1位,1表示负,0表示正,X 代表指数,8位,0到255转换为-127到128,M代表尾数,23位,注意 尾数总共24位,最高位等于1是隐含的。 JW-2A扭矩仪的数据处理流程如图2.4.2所示。
开始

清理缓存

激活设备 N Y 发送指令 设备是否成功激活

结束

等待完整返回

分析提取数据

接收数据

图2.4.2

JW2A数据处理流程

工控程序设计
11

另外,仪器处于释放状态时,每释放一组数据都会将数据从串口发送出来。数据 格式和发送的顺序同于用0命令读扭矩转速。 (3)LU-902M位式调节仪 LU-902M采用串行异步通讯,提供RS232C、422A或者485通讯接口,波特率有1200 、2400、4800、9600四种选择。每帧数据包含1个起始位(第0位),8个数据位(第18位),1个“寻址/数据”选择位(第9位,决定指令类别),1个停止位,共11位,数 据采用16进制表达。在每个通讯指令中,仪表最后返回信息 4FH、4BH表示通讯成功, 返回3FH,3FH表示通讯失败。 1)寻址指令 当上位机要对某LU-902M仪表进行通讯操作时,应先对其发寻址指令。寻址指令为 单字节指令,第1-8位为地址,第9位(校验位)为“1‖(非寻址指令为“0‖)。 2)数据读取指令 指令格式:45H 仪表返回:PV、SV、MV、4FH、4BH 其中PV为测量值、SV为设定值、MV为输出值,各占两个字节,低字节在前高字节在后 。 3)读参数指令 指令格式:52H + 参数代码 仪表返回:参数值 + 4FH、4BH 4)写参数指令 指令格式:57H + 参数代码 + 参数值 仪表返回:4FH、4BH 5)结束指令 指令格式:4FH或第9位为“1‖的非本机寻址指令 仪表无返回
工控程序设计
12

2 数据帧的校验 数据帧校验的目的是为了检查接收到的数据帧的完整性和准确性。和校 验位不同,校验位的作用是保证一个数据单位的正确性,是从微观角度考虑 。形象地说,校验位保证一个字正确,数据帧校验是保证一段话正确。 对数据帧进行校验的方法是:发送方根据要发送的数据帧,利用某种算 法计算出若干字节的校验码,并把校验码附加在数据帧中发送给接收端,接 收端接收到数据后,分离出校验码和数据本身,再根据发送方的算法,计算 出校验码,如果和接收到的校验码一致,则认为接收到的数据准确无误。串 口通信中常用的校验方式有累加和校验、异或和校验、循环冗余校验等。 (1)累加和校验 累加和校验的过程是,发送端将数据的每个字节累加求和,然后截取最 低字节(或最低两字节)作为校验码发送给接收端,接收端收到数据后,用 同样的方法计算累加和,并与发送过来的校验码进行比较,从而检验发送的 数据是否有误。 (2)异或和校验 异或和校验的过程是,发送端取出发送数据的第1个字节,与后续每个字 节依次按位异或,然后将计算结果(1个字节)作为校验码发送给接收端, 接收端收到数据后,用同样的方法计算异或和,并与发送过来的校验码进行 比较,从而检验发送的数据是否有误。
工控程序设计
13

(3)循环冗余校验 循环冗余校验(Cyclical Redundancy Check)简称CRC,它利用除法及余数的原 理来作错误侦测。实际应用时,发送端计算出校验码值并附加在数据帧中发送给接收 端,接收端对收到的数据重新计算校验码并与收到的校验码相比较,若两个值不同, 则说明数据通讯出现错误,要求发送端重新发送。 常用的CRC标准有CRC-12、CRC-16、CRC-CCITT和CRC-32,其中CRC-16和CRC-CCITT 用来作一个字节数据的校验,在串口数据校验中最常用的是 CRC-16。下面是循环冗余 校验码的计算过程: 1)设臵CRC寄存器,并给其赋值FFFF(hex)。 2)将数据的第一个字节与16位CRC寄存器的低8位进行异或,并把结果存入CRC寄存器。 3)CRC寄存器向右移一位,MSB(最高有效位)补零,移出并检查LSB(最低有效位)。 4)如果LSB为0,重复第三步;若LSB为1,CRC寄存器与多项式码相异或。 5)重复第3与第4步直到8次移位全部完成。此时一个字节数据处理完毕。 6)重复第2至第5步直到所有数据全部处理完成。 7)最终CRC寄存器的内容即为CRC值。 常用的循环冗余校验标准多项式为: CRC-16:X16+ X15+X2+1 CRC-CCITT:X16+X12 +X5+1 CRC-32:X32+X26+X23+X16+X12+X11+X10+ X8+X7+X5+X4+X2+X+1 其中CRC-16多项式对应多项式码为1000 0000 0000 0101(0x8005),CRC-CCITT对应 的多项式码为0001 0000 0010 0001(0x1021) 。
工控程序设计
14

2.4.4 任务实施 1 校验码的计算 在串口通信通,校验码在保证数据帧的正确性方面具有重要作用,虽然 在钻机性能测试系统中只用到累加和校验,但为了能应对串口通信中可能出 现的复杂情况,下面对三种校验码的计算都进行具体实现。 (1)累加和校验码的计算 累加和校验码可以取累加和的低8位(1个字节)或低16位(两个字节), 在下位机处理能力允许的情况下采用两个字节作校验码可以提高校验的准确 度。 下面的C#代码将累加和校验码生成功能封装到CCheck类的方法中,其中 CheckSum_1方法计算单字节累加和校验码,CheckSum_2方法计算双字节累加 和校验码 class CCheck { //函数功能: //计算字节数组b中从offset位臵开始的count个元素的单字节累加和校验码 //参数: //b:要计算累加和的字节数组 //offset:要计算累加和的元素的起始位臵 //count:要计算累加和的连续元素个数 工控程序设计 //返回值:累加和的低8位(单个字节)

15

public static byte CheckSum_1(byte[] b, int offset, int count) { uint sum = 0; for (int i = offset; i < offset + count; i++) sum += b[i]; //数组元素累加求和 return (byte)(sum & 0xFF); } //函数功能: //计算字节数组b中从offset位臵开始的count个元素的双字节累加和校验码 //参数: //b:要计算累加和的字节数组 //offset:要计算累加和的元素的起始位臵 //count:要计算累加和的连续元素个数 //返回值:长度为2的字节数组,第1个元素为累加和的低8位 //第2个元素为累加和的高8位

工控程序设计
16

public static byte[] CheckSum_2(byte[] b, int offset, int count) { uint sum = 0; byte[] tmp = new byte[2]; //存放累加和的低8位和高8位 for (int i = offset; i < offset + count; i++) sum += b[i]; //数组元素累加求和 tmp[0] = (byte)(sum & 0xFF); // 低8位在前 tmp[1] = (byte)((sum & 0xFF00) >> 8); // 高8位在后 return tmp; } } 下面的测试程序调用CheckSum_1方法,计算出长度为1个字节的累加和 校验码,并附加在字节数组末尾: byte[] b ={0x41,0x42,0x43,0}; //保留一个字节位臵存储校验码 byte sum = CCheck.CheckSum_1(b, 0, b.Length - 1); //计算单字节累加 和 b[b.Length-1] = sum; //附加在字节数组末尾
工控程序设计
17

下面的测试程序调用CheckSum_2方法,计算出长度为两个字节的累加和 校验码,并附加在字节数组末尾: byte[] b ={0x41,0x42,0x43,0,0}; //保留两个字节位臵存储校验码 byte[] sum = CCheck.CheckSum_2(b, 0, b.Length - 2); //计算双字节累 加和 b[b.Length-2] = sum[0]; //附加在字节数组末尾 b[b.Length-1] = sum[1]; (2)异或和校验码的计算 下面的C#代码将异或和校验码生成功能封装到CCheck类的方法中,方法 名为XorSum: class CCheck { //函数功能: //计算字节数组b中从offset位臵开始的count个元素的单字节异或和校验码 //参数: //b:要计算累加和的字节数组 //offset:要参与计算校验码的元素的起始位臵 //count:要参与计算校验码的连续元素个数 工控程序设计 //返回值:异或和(8位,1个字节)
18

public static byte XorSum(byte[] b, int offset, int count) { byte sum = 0; for (int i = offset; i < offset + count; i++) sum ^= b[i]; //依次和每个元素进行按位异或运算 return sum; } } 下面的测试程序调用XorSum方法,计算出长度为1个字节的异或和校验 码,并附加在字节数组末尾: byte[] b ={0x41,0x42,0x43,0}; //保留一个字节位臵存储校验码 byte xorsum = CCheck. XorSum(b, 0, b.Length - 1); //计算异或和 b[b.Length-1] = xorsum; //附加在字节数组末尾
工控程序设计
19

(3)循环冗余校验码的计算 下面的C#代码将CRC-16校验码生成功能封装到CCheck类的方法中,方法 名为CRC16: class CCheck { //函数功能: //计算字节数组b中从offset位臵开始的count个元素的循环冗余校验码 //参数: //b:要计算累加和的字节数组 //offset:要参与计算校验码的元素的起始位臵 //count:要参与计算校验码的连续元素个数 // CrcPoly:CRC多项式对应的16位二进制编码 //返回值:长度为2的字节数组,第1个元素为校验码的低8位 //第2个元素为校验码的高8位

工控程序设计
20

public static byte[] CRC16(byte[] b, int offset, int count, ushort CrcPoly) { ushort CrcReg = 0xFFFF; //16位CRC寄存器 uint LSB; //最低有效位 byte[] tmp = new byte[2]; for (int i = offset; i < offset + count; i++) { CrcReg ^= b[i]; // 将当前字节与CRC寄存器的低8位进行按位异或 for (int n = 1; n <= 8; n++) //进行8次逻辑移位 { LSB = CrcReg & 1u; //―u‖表示该常量是无符号整型 CrcReg >>= 1; //CRC寄存器逻辑右移1位 if (LSB == 1) CrcReg ^= CrcPoly; } }
工控程序设计
21

tmp[0] = (byte)(CrcReg & 0xFF); //低8位在前 tmp[1] = (byte)((CrcReg & 0xFF00) >> 8); //高8位在后 return tmp; } } 下面的测试程序调用CRC16方法,计算出长度为两个字节的循环冗余校验 码,并附加在字节数组末尾: byte[] b ={0x41,0x42,0x43,0,0}; //保留两个字节位臵存储校验码 byte[] crc = CCheck. CRC16 (b, 0, b.Length – 2 ,0x8005); //计算循 环冗余校验码 b[b.Length-2] = sum[0]; //附加在字节数组末尾 b[b.Length-1] = sum[1];

工控程序设计
22

2 上位机主动模式串口通信类的设计
煤矿钻机性能测试系统中用到的HC-100智能测控仪、JW-2A扭矩仪和LU902M位式调节仪与HSDZC电能综合测试仪不同,这些设备需要上位机主动发 送命令,才给予回复,因此可以把这三种设备的通信模式归为“上位机主动 模式”。 下面将设计上位机主动模式的串口通信类CActiveCOMHelper,和 CPassiveCOMHelper类不同的是,CActiveCOMHelper类中增加了一个名为 command的成员,存放当前应该执行的命令,线程函数根据该成员的相关属 性,就知道现在应该给下位机发送什么命令。由于采用“提问——回答”的 方式,能够准确控制下位机发送来的数据,故不再需要队列来暂存接收的数 据。 CCommand类实现代码如下: public class CCommand { public string commandName;//命令名字 public byte[] cmdContent;//命令的内容 public Parity parity = Parity.None;//发送命令时采用的校验方式 public byte[] replyContent;//回复内容
工控程序设计
23

public int replyLength;//回复内容的规定长度 public bool isCompleted; //命令是否完成 public CCommand() //创建对象时commandName不能为null,以免后面比较时 出错 { this.commandName = ""; } } command成员(CCommand的实例)是UI模块和CActiveCOMHelper对象的 连接纽带。UI模块首先查询前一个命令是否完成(isCompleted是否为true ),若已经完成,就设臵下一条命令的名字、内容、校验方式、回复内容的 规定长度,交给CActiveCOMHelper对象去执行;CActiveCOMHelper对象执行 完命令后,把回复内容填好,并将完成标志isCompleted设臵为true。如此 循环,就可以不断让CActiveCOMHelper对象执行命令。这样分工的好处是, 处于上层的UI模块没有必要知道如何和串口通信、如何创建线程等等复杂琐 碎的具体操作,而只要知道该发什么命令,以及如何处理收到的结果。
工控程序设计
24

CActiveCOMHelper类实现代码如下: public class CActiveCOMHelper { public int readTimeOut;//接收数据超时毫秒数 public string portName;//设备所占用的串口名 private bool stopFlag;//线程循环停止标志 private int baudRate, dataBits; private StopBits stopBits; private CCommand command;

public delegate void DataReceivedHandler(CActiveCOMHelper sender); public event DataReceivedHandler DataReceived;
public CActiveCOMHelper(string portName, int baudRate, int dataBits, StopBits stopBits)
工控程序设计
25

{

this.portName = portName; this.baudRate = baudRate; this.dataBits = dataBits; this.stopBits = stopBits; this.readTimeOut = 3000;//默认3000毫秒读取超时 this.command = new CCommand(); //始终保持有一个命令 this.command.isCompleted = true; //该命令开始时处于完成状态
} public void start() { Thread t = new Thread(new ThreadStart(threadFun)); t.IsBackground = true; stopFlag = false; t.Start(); }

工控程序设计
26

public void stop() { stopFlag = true; } public CCommand getCommand() //获取一个克隆的命令对象 { CCommand cmd = new CCommand(); Monitor.Enter(this); //线程同步:在进行get操作时,不准进行set操作 cmd.commandName = command.commandName; cmd.cmdContent = command.cmdContent; cmd.parity = command.parity; cmd.replyContent = command.replyContent; cmd.replyLength = command.replyLength; cmd.isCompleted = command.isCompleted; Monitor.Exit(this); return cmd; }
工控程序设计
27

public void setCommand(CCommand cmd) { Monitor.Enter(this); //线程同步:在进行set操作时,不准进行get操作 this.command = cmd; Monitor.Exit(this); } protected virtual bool checkReply(string cmdType, byte[] bRecv) { return false; } private void threadFun() { SerialPort sp = new SerialPort(portName, baudRate, Parity.None, dataBits, stopBits); try { if (sp.IsOpen) sp.Close(); sp.Open(); 工控程序设计 }

28

catch { return; } byte first; byte[] bRecv, bSend; while (!stopFlag) { CCommand cmd = getCommand(); if (cmd.isCompleted == false) try { sp.ReadTimeout = readTimeOut; sp.ReadExisting(); //清除接收缓冲区 bSend = cmd.cmdContent;//要发送的内容 sp.Parity = cmd.parity;//校验位 sp.Write(bSend, 0, bSend.Length); first = (byte)sp.ReadByte(); // 阻塞方式读取数据
工控程序设计
29

bRecv = new byte[cmd.replyLength];//存储回复数据(字节数已经规定) bRecv[0] = first; int n = 0;//检查的次数 while (n <= 3 && sp.BytesToRead < cmd.replyLength - 1) { n++; Thread.Sleep(20); //等待20毫秒后再检查数据是否达到规定长度 } if (n > 3) continue; //等待3次后数据还未到达 sp.Read(bRecv, 1, cmd.replyLength - 1); if (checkReply(cmd.commandName, bRecv)) { cmd.replyContent = bRecv;//填写回复内容 cmd.isCompleted = true;//修改命令完成标志 setCommand(cmd); if (DataReceived != null) //引发事件,通知使用该类的程序 DataReceived(this); } }
工控程序设计
30

catch { } else Thread.Sleep(20);//没有新命令下达,延时防止CPU占用率过高 } if (sp != null && sp.IsOpen) sp.Close();

}
}

在前一个步骤中已经实现了串口数据接收、解析和显示的功能,并且在 工作者线程中接收和处理数据,在此期间前台的UI线程能响应用户输入。从 功能上看,已经能够满足用户需求,但是从代码的组织和管理角度看,该程 序还有比较大问题:负责接收和处理数据的代码和负责显示的代码混杂在一 起,不能重复使用,若增加一个同类设备,很多代码还要重写,而且给查看 和调试程序也带来较大困难。下面用面向对象方法对程序进行重新设计。

工控程序设计
31

3 HC-100智能测控仪数据采集
HC-100智能测控仪采用以上位机为主导的“提问——应答”通信模式, 管理该设备的类可以从CActiveCOMHelper类派生而来,命名为CHC100,在类 中覆盖checkReply方法,并实现浮点数的解码方法AsciiToFloat。该类的实 现代码如下: public class CHC100 : CActiveCOMHelper { public CHC100(string portName, int baudRate, int dataBits, StopBits stopBits) : base(portName, baudRate, dataBits, stopBits) { } //根据命令名称构建命令对象 public CCommand makeCommand(string commandName) { CCommand command = new CCommand();
工控程序设计
32

switch (commandName) { case "R": //读数据指令 command.commandName = commandName; command.replyLength = 11; command.cmdContent = new byte[] { 0x04, 0x30, 0x31, 0x52, 0x30, 0x30, 0x05 }; break; } return command; } protected override bool checkReply(string cmdType, byte[] bRecv) { switch (cmdType) { case "R": //读数据指令 if (bRecv[0] == 0x02 && bRecv[10] == 0x03) if (bRecv[9] == CCheck.CheckSum_1(bRecv, 0, 9)) return true; 工控程序设计 break; }

33

return false; } public float AsciiToFloat(byte[] b) { StringBuilder s = new StringBuilder(); for (int i = 3; i <= 8; i++) //下标为3到8的数据段表示浮点数值 { s.Append((char)b[i]); //将字符连接为字符串 } float f = 0F; try { f = Single.Parse(s.ToString()); //字符串转换为浮点数 } catch { } return f; } }

工控程序设计
34

下面的代码说明了CHC100类在UI模块中的应用。后台工作者线程获得下 位机应答数据后,存储在CCommand对象中,不用事件通知UI线程。UI线程每 隔1秒钟检查一次命令执行情况,如果命令执行完毕,则读取本次的应答数 据,并设臵下一次的命令。 private CHC100 hc100; private void btnStart_Click(object sender, EventArgs e) //开始采集 数据 { hc100 = new CHC100("COM1", 9600, 8, StopBits.One); hc100.start(); timer1.Enabled = true; } private void btnStop_Click(object sender, EventArgs e) //结束采集 { timer1.Enabled = false; hc100.stop(); }
工控程序设计
35

private void timer1_Tick(object sender, EventArgs e) { CCommand cmd = hc100.getCommand(); //获取当前命令 if (cmd.isCompleted) //当面命令已完成 { CCommand nextCmd = hc100.makeCommand("R"); //构建下一个命令 hc100.setCommand(nextCmd); if (cmd.cmdContent != null) Trace.WriteLine("HC100回复:压力:" + AsciiToFloat(cmd.replyContent)); } else Trace.WriteLine(cmd.commandName + "命令还未完成"); }

工控程序设计
36

4 JW-2A扭矩仪数据采集 JW-2A扭矩仪用于测量钻机的扭矩和转速,该设备和HC-100智能测控仪 相似,也采用以上位机为主导的“提问——应答”通信模式,不同的是通信 方式比HC-100智能测控仪稍微复杂,每次通信需要发送两道命令,首先发寻 址指令激活,再发送读数据指令获取数据。 管理该设备的类从CActiveCOMHelper类派生而来,命名为CJW2A,代码 如下: class CJW2A : CActiveCOMHelper { public CJW2A(string portName, int baudRate, int dataBits, StopBits stopBits) : base(portName, baudRate, dataBits, stopBits) { } public CCommand makeCommand(string commandName) // 根据命令名 称构建命令对象 { CCommand command = new CCommand();

工控程序设计
37

switch (commandName) { case "A": //寻址指令(激活设备) command.commandName = commandName; command.replyLength = 1; command.parity = Parity.Mark; command.cmdContent = new byte[] { 0x0A }; break; case "R": //读数据指令 command.commandName = commandName; command.replyLength = 10; command.parity = Parity.Space; command.cmdContent = new byte[] { 0x00 }; break; } return command; }
工控程序设计
38

protected override bool checkReply(string cmdType, byte[] bRecv) { switch (cmdType) { case "A": //寻址指令 if (bRecv[0] == 0x0A) return true; break; case "R": //读数据指令 if (bRecv[0] == 0x02 && bRecv[10] == 0x03) if (bRecv[9] == CCheck.CheckSum_1(bRecv, 0, 9)) return true; break; } return false; }
工控程序设计
39

public float HexToFloat(byte[] data) //4字节转换为浮点数(按照 IEEE754规范) { int s; //符号位,+1或者-1。 int e; //指数,根据IEEE754,指数部份算出来后要减127 float m; //小数位。 float mreturn; //返回的浮点数,应为:(1+小数位)* 2^(指数)* (符号位) s = data[0] & 128; if (s == 128) s = 1; e = (data[0] & 127) * 2 + (data[1] & 128) / 128; m = (data[1] & 127) * 65536 + data[2] * 256 + data[3]; m = m / 8388608; mreturn = (float)((m + 1) * Math.Pow((-1), s) * Math.Pow(2, (e - 127))); return mreturn; } }
工控程序设计
40

在UI模块中,每个CJW2A对象负则和一个JW-2A扭矩仪通信,由于每次通 信过程要发送两道命令,所以代码中采用事件通知方式来使两道命令连贯地 发送,当收到第一道命令的回复后,引发事件,在事件处理程序中紧接着发 第二道命令。代码如下: private CJW2A jw2a; private void btnStart_Click(object sender, EventArgs e) { jw2a = new CJW2A("COM3", 4800, 8, StopBits.One); jw2a.DataReceived += new CActiveCOMHelper.DataReceivedHandler(DataReceived); jw2a.start(); timer1.Enabled = true; } private void btnStop_Click(object sender, EventArgs e) { timer1.Enabled = false; jw2a.stop(); }
工控程序设计
41

private void DataReceived(CActiveCOMHelper sender) { CCommand cmd, nextCmd; switch (sender.portName.ToUpper()) { case "COM3": //JW2A要先发“A‖命令,完成后紧接着发“R‖命令 cmd = jw2a.getCommand(); if (cmd.commandName == "A" && cmd.isCompleted) //读数命令已完成 { nextCmd = jw2a.makeCommand("R"); //JW2A要先发A命令 jw2a.setCommand(nextCmd); Trace.WriteLine("JW2A的A命令已完成,立即发送R命令。"); } break; } } private void timer1_Tick(object sender, EventArgs e) { CCommand cmd, nextCmd; 工控程序设计 //处理JW2A的返回结果

42

cmd = jw2a.getCommand(); if (cmd.commandName == "R" && cmd.isCompleted) //读数命令已完成 { nextCmd = jw2a.makeCommand("A"); //JW2A要先发A命令 jw2a.setCommand(nextCmd); if (cmd.cmdContent != null) { float nj, zs; byte[] tmp = new byte[4]; Array.Copy(cmd.replyContent, 0, tmp, 0, 4); nj = jw2a.HexToFloat(tmp); //扭矩 Array.Copy(cmd.replyContent, 4, tmp, 0, 4); zs = jw2a.HexToFloat(tmp); //转速 Trace.WriteLine("JW2A回复:扭矩" + nj + ",转速" + zs); } } else Trace.WriteLine("JW2A的" + cmd.commandName + "命令还未完成"); }
工控程序设计
43

5 LU-902M位式调节仪数据采集
LU-902M位式调节仪在钻机性能测试系统中应用最广泛,用于测量大气 温度、马达温度、泵温度、油温、冷却水流量、马达进油流量、马达回油流 量、马达油压、操纵台噪音、马达噪音等诸多参数,该设备同样采用以上位 机为主导的“提问——应答”通信模式,它仍然需要激活,不过只激活一次 ,以后就象HC-100智能测控仪一样发送查询指令获取数据。 管理该设备的类从CActiveCOMHelper类派生而来,命名为CLU902M,代码 如下: class CLU902M : CActiveCOMHelper { public bool isActive; //设备激活标志 public byte address; //设备地址 public CLU902M(string portName, int baudRate, int dataBits, StopBits stopBits) : base(portName, baudRate, dataBits, stopBits) { } //根据命令名称构建命令对象
工控程序设计
44

public CCommand makeCommand(string commandName) { CCommand command = new CCommand(); switch (commandName) { case "A": //寻址指令(激活设备) command.commandName = commandName; command.replyLength = 2; command.parity = Parity.Mark; command.cmdContent = new byte[] { address }; break; case "R": //读数据指令 command.commandName = commandName; command.replyLength = 8; command.parity = Parity.Space; command.cmdContent = new byte[] { 0x45 }; break; } return command; }

工控程序设计
45

protected { switch { case if {

override bool checkReply(string cmdType, byte[] bRecv)
(cmdType) "A": //寻址命令 (bRecv[0] == 0x4F && bRecv[1] == 0x4B) this.isActive = true; return true;

} break; case "R": //读数据指令 if (bRecv[6] == 0x4F && bRecv[7] == 0x4B) return true; break;

} return false;
}
工控程序设计
46

public float HexToFloat(byte H, byte L) { return (float)(H * 256 + L) / 10.0F; } } 在UI模块中,每个CLU902M对象负则和一个LU-902M位式调节仪通信,程 序先判断设备是否激活,未激活前先发寻址命令激活设备,对已经激活的设 备,UI线程每隔1秒钟发送读数命令获取参数值。代码如下: private CLU902M lu902m; private void btnStart_Click(object sender, EventArgs e) { lu902m = new CLU902M("COM5", 9600, 8, StopBits.One); lu902m.address = 0x00;//设备地址 lu902m.start(); timer1.Enabled = true; } private void button2_Click(object sender, EventArgs e) { timer1.Enabled = false; 工控程序设计 lu902m.stop(); }

47

private void DataReceived(CActiveCOMHelper sender) { CCommand cmd, nextCmd; switch (sender.portName.ToUpper()) { case "COM5": //LU902M要先用寻址指令激活 if (!lu902m.isActive) { cmd = lu902m.getCommand(); if (cmd.commandName == "A" && cmd.isCompleted) //读数命令已完 成 { lu902m.isActive = true; Trace.WriteLine("LU902M已经激活。"); } } break; } }
工控程序设计
48

private void timer1_Tick(object sender, EventArgs e) { //处理LU902M的返回结果 cmd = lu902m.getCommand(); if (cmd.isCompleted) { nextCmd = new CCommand(); if (!lu902m.isActive) //未激活,发寻址指令 { nextCmd.commandName = "A"; nextCmd.parity = Parity.Mark; nextCmd.cmdContent = new byte[] { lu902m.address }; } else { nextCmd.commandName = "R"; nextCmd.parity = Parity.Space; nextCmd.cmdContent = new byte[] { 0x45 }; }

工控程序设计
49

lu902m.setCommand(nextCmd); if (cmd.cmdContent != null) { float mdwd = lu902m.HexToFloat(cmd.replyContent[1], cmd.replyContent[0]); Trace.WriteLine("LU902M回复:马达温度" + mdwd); } } else Trace.WriteLine("LU902M的" + cmd.commandName + "命令还未完成"); } 至此已经面向对象设计方法完成了HC-100智能测控仪、JW-2A扭矩仪、 LU-902M位式调节仪数据采集程序的设计,读者把代码进行简单的组合,就 可以同时对多个不同种类的设备进行数据采集,并每秒刷新一次参数值。本 章学习完成了钻机性能测试系统的数据采集部份功能。

工控程序设计
50

2.4.5 考核要点
考核内容 分数

设计代码实现累积和、异或和、循环冗余校验
设计上位机主动模式串口通信类 设计HC-100智能测控仪、JW-2A扭矩仪、LU-902M位式调节仪数据采集类,并 在UI模块中调用

20%
20% 60%

2.4.6 能力扩展
1.常用的对数据帧进行校验的校验码有那几种? 2. HC-100智能测控仪、JW-2A扭矩仪、LU-902M位式调节仪的通信过程有什么相同点 和不同点? 3.编写程序对HC-100智能测控仪、JW-2A扭矩仪、LU-902M位式调节仪进行模拟。 4.修改HC-100智能测控仪、JW-2A扭矩仪、LU-902M位式调节仪的 数据采集程序,使 采集到的数据显示在文本框中? 5.将本节中的程序和HSDZC电能综合测试仪的程序结合起来,形成一个能够同时采集 多个仪器数据的完整程序。
工控程序设计
51


相关文章:
多个串口设备数据的连续采集_图文.ppt
多个串口设备数据的连续采集 - 工控程序设计 学习情景2.4 多个串口设备数据的连续采集 学习情景2.4 多个串口设备数据的连续采集 2.4.1 学习要点 1.知识点: 煤矿...
4_多个串口设备数据的连续采集_图文.ppt
4_多个串口设备数据的连续采集 - 工控程序设计 学习情景2.4 多个串口设备数据的连续采集 学习情景2.4 多个串口设备数据的连续采集 2.4.1 学习要点 1.知识点: ...
汽车磅串口连续输出数据的采集方法.doc
汽车磅串口连续输出数据的采集方法_交通运输_工程科技_专业资料。汽车磅串口连续...4_多个串口设备数据的连... 103人阅读 51页 2下载券 基于双串口和多进程...
多串口数据采集技术在生产调度监测系统中的应用.pdf
多串口数据采集技术在生产调度监测系统中的应用 - 本文由米尔自动化网 http://www.mirautomation.com/整理推荐 多串口数据采集技术在生产调度监测系统中的应用 一...
一种多串口多线程数据采集方法的设计与应用_胡双炎_图文.pdf
一种多串口多线程数据采集方法的设计与应用_胡双炎 - Vol. 9 No. 2
一个多串口多线程数据采集系统软件的设计与实现.pdf
一个多串口多线程数据采集系统软件的设计与实现_IT/...然后就进入到循环等待本串口的开始连接信号到来的状...并根据上位机的要 求将数据送出或对本地设备进行...
汽车磅串口连续输出数据的采集方法_论文.pdf
汽车磅串口连续输出数据的采集方法 - 主要描述了梅特勒-托利多8142型称重显示仪表的串口数据的读取方法,在主要部分还使用了UML软件建模来辅助说明设计思路。
基于多线程串口通信的生产数据实时采集系统.pdf
基于多线程串口通信的生产数据实时采集系统_天文/地理...各个设备的工作状态和产品在整个生产过程中各个工序...不断循环的发送命令给终端询问终端是否采集数据,然后...
LABVIEW上位机双串口同步方法与数据采集.doc
LABVIEW上位机双串口同步方法与数据采集_职业规划_...温度、电阻率、伽马 值的大小和描绘出的连续变化...的设备或接口读取指定数 量的字节,并将数据返回至...
(WIFI)10个串口设备通过602连到路由器将数据采集到一个....pdf
(WIFI)10个串口设备通过602连到路由器将数据采集到一个计算机上_计算机硬件及网络_IT/计算机_专业资料。WIFI模块产品引用案例分享 2 连到路由器将数据采集到一个 ...
多串口环境下数据采集与处理的研究.doc
多串口环境下数据采集与处理的研究 - 虚拟串口技术的出现,为传统的RS-485通讯开辟了新天地,使得基于多串口的应用成为可能。文章对多串口环境下数据采集与处理的实现...
一种多串口多线程数据采集方法的设计与应用_论文.pdf
一种多串口多线程数据采集方法的设计与应用 - 本文设计和实现了一种基于多串口多线程的方法,可以把多个下位机采集的信号实时的显示到中心检测计算机上,从而能足不...
一种基于多通道缓冲串口的数据采集系统的设计_图文.pdf
一种基于多通道缓冲串口的数据采集系统的设计_信息与...客观 地反映设备的运行 状态 实践证明振 动监 测 ...连续 方式和 D S P 的接口的时 序图 , , , ...
基于以太网的多串口数据采集系统及应用_图文.pdf
基于以太网的多串口数据采集系统及应用 - ! 基于以太网的多串口数据采集系统及应用 基于以太网的多串口数据采集系统及应用 &’()*+,-.*/( 01.), 2/)/ ...
多套DCS联网数据采集解决方案.doc
多套DCS联网数据采集解决方案 0 引言 我公司中心...各个DCS设备技术虽然不同, 但都符合国际标准的开放...由于仪峰系 统OPC软件接口是通过串口开放数据的,...
采用FPGA实现同步串行数据的并行采集.pdf
这为具有同步串行接口 设备的数据并行采集提供了一种低成本的实现方案 。 关键...TMS320F28332 芯片有多个采用非复用异步总线 的片外存储器与外部接口 XINTF [...
基于USB2.0的多串口数据采集设计_论文.pdf
基于USB2.0的多串口数据采集设计 - 己口I 7年I 1月 第] 5卷第1 I期 研究与开发 基于USB2. 0的多串 口数 据 ...
设备联网-数据采集技术解决方案_图文.pdf
采集数据较全面; 4、NC程序传输; 数控设备网络架构网口设备 串口设备 DI
用VisualBasic实现智能仪表的微机串口连续监测与记录系统.pdf
1 数据采集与转换 参照智能仪表的串口协议 , 通讯...数据链包括设备 地址 、、 命令 数据 、 校验等内容...通过延时程序和循环语句 ,可编程实现多个智能仪表 ...
基于FPGA的多通道串口数据采集与传输设计_论文.pdf
基于FPGA的多通道串口数据采集与传输设计 - 计算 机测 量与控 制. 201
更多相关标签: