你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:杂志经典 / 专家论坛
计算机监控系统仿真开发平台的设计与实现(上)
 

摘 要 计算机监控系统广泛应用于众多领域。本文在 Vista 环境下,利用 Visual Basic .NET 2008 工具开发了一个模拟量输入/开关量输出的仿真模块,设计了该模块的通信协议;开发了RS-232/RJ-45RS-232/RS-232协议转换器,延长了计算机监控的通信距离,并可以实现计算机监控系统的远程维护与协议截取;此外,还开发了通用串口设备测试工具。利用以上软件模块和工具可以在计算机房零成本搭建多种形式的计算机监控系统仿真开发平台,并对其进行综合测试。

关键词 仿真模块,协议转换器,串行通信,测试工具, DataGridView

 

计算机监控系统是以监测控制计算机为主体,加上检测装置、执行机构与被监测控制的对象共同构成的整体。在这个系统中,计算机直接参与被监控对象的检测、监督和控制[1]。检测主要是通过传感器和相应的输入模块来取得被监控对象的状态数据,监督主要是对状态数据进行分析后给操作员提供手动操作的参考,控制则是手动或按照一定的策略自动地对被监控对象执行相应的操作。由此可见,检测与控制模块是计算机监控系统直接跟被监控对象关联的不可或缺的输入输出(I/O)模块,学习和研究这些模块对计算机监控系统的辅助开发、测试与教学等都具有重要意义。

I/O模块一般配置有串行通信接口,本文用软件仿真一个2路模拟量输入和2路数字量(又称开关量)输出的基于 RS-232 接口的模块,并开发了针对该仿真模块的主控程序;开发了 RS-232/RS-232 转换器,可以将两个不同通信协议和不同波特率的主控机或受控机连接起来,并可以截取两者之间的通信协议; 开发了RS-232/RJ-45协议转换器软件,可以将串行通信协议转换为 TCP 协议,而且,该软件既可以工作在客户机方式,又可以工作在服务器方式,借助该协议转换器,可以对传统的基于 RS-232 接口的监控系统转换为基于 Internet 的监控系统,从而进行远程测试;通用串口设备测试工具可以自动生成多种校验码及添加多种结尾码,既可以充当主控机对受控机进行测试,也可以充当受控机对主控机进行测试,并可以记录测试结果和通信协议。在这些仿真模块与协议转换器的基础之上,可以在高校的计算机房零成本搭建多种形式的计算机监控系统的仿真开发平台,并可对其进行综合测试,从而节约大量教学设备经费的投入及相关项目开发费用的支出。

 

一、串行通信

通信是计算机监控系统实现的关键。串行通信的基本技术主要包括数据处理技术、数据的校验技术以及串口操作技术,串口数据接收技术则是关键技术。有了这些技术,就可以自动生成指定格式的数据包、校验数据包,也可以非常便捷地通过串行接口发送和接收数据。

1.数据处理技术

Visual Basic 2008 提供了丰富的字符串处理函数,利用16进制字符串表示数据,可以较好地观察数据,例如 0xFF 是一个不可见字符,如果以 "FF" 来显示则比较清楚。BytesToHexChars 函数实现字节序列到16进制字符串的转换,其中,通过 ByteToTwoHexChars 函数(其定义比较简单,略),将一个字节转换为标准的两个16进制字符,然后,对这些字符进行累加。

 

    Public Function BytesToHexChars(ByRef byteArray As Byte()) As String

        Dim I As Integer

        Dim strData As String = ""

 

        For I = 0 To byteArray.Length - 1

            strData &= ByteToTwoHexChars(byteArray(I))

        Next I

 

        Return strData

    End Function

 

在发送字节序列时,又需要将16进制字符串转换为字节序列,这可以通过 HexCharsToBytes 来实现,其基本原理是调用 TwoHexCharsToByte 函数将两个16进制字符转换为一个字节。

 

    Public Function HexCharsToBytes(ByVal strVal As String) As Byte()

        Dim I As Integer

        Dim nLength As Integer

        Dim bTmp() As Byte

 

        If strVal = "" Then Return Nothing

 

        strVal = Trim(strVal)           '删除尾部空格

        nLength = Len(strVal) \ 2 - 1   '求得字节长度

        ReDim bTmp(nLength)             '可变数组保存字节序列

 

        For I = 0 To nLength

            '每两个16进制字符转换为一个字节,存入可变数组

            bTmp(I) = TwoHexCharsToByte((Mid(strVal, I * 2 + 1, 2)))

        Next I

 

        Return bTmp

    End Function

 

在计算机监控系统中,经常用一个字节的8位来表示8个开关的状态,0表示开关打开,1表示开关闭合。这时,就需要对字节中的某位进行测试、置位(置1)和复位(置0)。字节的位测试通过 And 运算实现,如果结果不为0,则返回 True,否则返回 False

 

    Public Function CheckByteBit(ByVal bData As Byte, ByVal nBit As Integer)  _

                As Boolean

        Dim bTmp As Byte

        Dim bResult As Byte

 

        If nBit > 7 Or nBit < 0 Then Return False

        bTmp = 2 ^ nBit

        bResult = bData And bTmp

        If bResult <> 0 Then

            Return True

        Else

            Return False

        End If

    End Function

 

字节的某位置位通过 Or 运算实现,其源代码如下。

 

    Public Function SetByteBit(ByVal bData As Byte, ByVal nBit As Integer) As Byte

        Dim bTmp As Byte

 

        If nBit > 7 Or nBit < 0 Then

            Return bData

        End If

 

        bTmp = 2 ^ nBit

        Return bData Or bTmp

    End Function

 

字节中某位的复位通过 Xor And 运算共同完成。首先,通过 Xor 运算使得临时变量 bTmp 的该位为0,其它位为1,再通过 And 运算进行复位。

 

    Public Function ResetByteBit(ByVal bData As Byte, ByVal nBit As Integer) As Byte

        Dim bTmp As Byte

 

        If nBit > 7 Or nBit < 0 Then

            Return bData

        End If

 

        bTmp = (2 ^ nBit) Xor &HFF

        Return bData And bTmp

    End Function

 

2.数据的校验技术

计算机监控系统的通信协议一般包括前导字符、地址、读写功能标志码、校验码与结尾码。相同类型的模块一般采用相同的前导字符,而地址主要用来区分模块,因为一台计算机可能要连接多个模块,读功能码主要用来读取模块的状态或数据,写功能码主要用来设置模块的工作方式或控制输出开关,校验码(可选)主要用来对当前数据块进行校验,一般有异或(Xor)、累加和(Add)和循环冗余(CRC)校验码等。相同类型的设备一般也有相同的结尾码(可选),一般取CR0x0d)或CRLF0x0d0a)作为结尾码。为了节省篇幅,这里仅以 Xor 校验码为例进行说明。

首先,定义枚举类型 CheckMode 表示数据包的校验方式,0表示没有校验,1表示 Xor2表示 Add3表示 CRC4表示BCSTCP 协议中的累加求补校验),其中,Xor Add 生成一个字节的校验码,CRC BCS 生成两个字节的校验码。

 

    Public Enum CheckMode

        Chk_None = 0

        Chk_Xor = 1

        Chk_Add = 2

        Chk_CRC = 3

        Chk_BCS = 4

    End Enum

 

然后,定义枚举类型 EndMark 表示数据包的结尾标志,0表示没有结尾码,1表示以回车符作为结尾码,2表示以回车换行作为结尾码(这种结尾码可用于 POP3SMTP等网络协议)。

 

    Public Enum EndMark

        Add_None = 0

        Add_CR = 1

        Add_CRLF = 2

    End Enum

 

Xor 校验码通过函数 xorStrValue 进行,输入16进制字符串,输出两个16进制字符。首先,将输入的16进制字符串 strHexData 转换成字节数组 byteBuffer,然后,以初始值0逐个字节异或,最后的字节通过 ByteToTwoHexChars 转换为两个16进制字符返回。

 

    Public Function xorStrValue(ByVal strHexData As String) As String

        Dim I As Integer

        Dim xorTmp As Byte

        Dim byteBuffer As Byte()

 

        byteBuffer = HexCharsToBytes(strHexData)

 

        xorTmp = 0

 

        For I = 0 To byteBuffer.Length - 1

            xorTmp = xorTmp Xor byteBuffer(I)

        Next I

 

        Return ByteToTwoHexChars(xorTmp)

    End Function

 

如果一个字节序列的最后一个字节是前面所有字节的异或校验码,那么,整个字节序列的异或校验码将为0。例如,字节 30 是 字节序列 2F 1F 的异或校验码,则字节序列 2F 1F 30 的异或校验码应该为0CheckXorStrValue 函数正是根据这个原理,如果计算所得的校验码为 "00",则表示数据包校验正确,返回 True,否则,返回 False

 

    Public Function CheckXorStrValue(ByVal strHexData As String) As Boolean

        If xorStrValue(strHexData) = "00" Then

            Return True

        Else

            Return False

        End If

    End Function

 

添加结尾码函数 AddEndMark 函数比较简单,只要根据参数添加 "0D" "0D0A" 或什么都不添加,直接返回原来的16进制字符串即可。对应的 CheckEnd 函数,也只是根据参数检查结尾码,不涉及到计算,只需要通过字符串处理函数完成。

CheckParity 函数检查数据包的各种校验是否正确,正确则返回 True,错误,则返回 False

 

    Public Function CheckParity(ByVal strHexData As String, ByVal nCheckMode _

                            As CheckMode) As Boolean

        Select Case nCheckMode

            Case CheckMode.Chk_Xor

                Return CheckXorStrValue(strHexData)

            Case CheckMode.Chk_Add

                Return CheckAddStrValue(strHexData)

            Case CheckMode.Chk_CRC

                Return CheckCrcStrValue(strHexData)

            Case CheckMode.Chk_BCS

                Return CheckAddStrBCSValue(strHexData)

        End Select

 

        Return True  'CheckMode.Chk_None

    End Function

 

GetFullPackage 函数生成综合数据包,自动添加校验码和结尾码。GetEvenUCaseString 函数将输入的16进制字符串转换成大写,并删除最后的奇数字符。

 

    Public Function GetFullPackage(ByVal strHexData As String, ByVal nParity _

                 As CheckMode, ByVal nEndMark As EndMark) As String

        Dim strTmp As String

        Dim strPackage As String = ""

 

        strTmp = GetEvenUCaseString(strHexData)

        Select Case nParity

            Case CheckMode.Chk_None

                strPackage = strTmp

            Case CheckMode.Chk_Xor

                strPackage = strTmp & xorStrValue(strTmp)

            Case CheckMode.Chk_Add

                strPackage = strTmp & addStrValue(strTmp)

            Case CheckMode.Chk_CRC

                strPackage = strTmp & crcStrValue(strTmp)

            Case CheckMode.Chk_BCS

                strPackage = strTmp & addStrBCSValue(strTmp)

        End Select

 

        Return AddEndMark(strPackage, nEndMark)

    End Function

 

CheckPackage 函数对包含校验码和结尾码的数据包进行校验,正确则返回 True,错误则返回 False。首先调用 CheckEnd 函数检查结尾码,如果错误,则直接返回 False,程序结束,否则,数据包删除结尾码后调用 CheckParity 检查校验码。

 

    Public Function CheckPackage(ByVal strHexData As String, ByVal nParity _

                As CheckMode, ByVal nEndMark As EndMark) As Boolean

        Dim strTmp As String

 

        strTmp = UCase(strHexData)

        If CheckEnd(strTmp, nEndMark) = False Then Return False

 

        Select Case nEndMark

            Case EndMark.Add_CR

                strTmp = Mid(strTmp, 1, Len(strTmp) - 2)

            Case EndMark.Add_CRLF

                strTmp = Mid(strTmp, 1, Len(strTmp) - 4)

        End Select

 

        Return CheckParity(strTmp, nParity)

    End Function

 

3.串口操作技术

串口一般通过下拉框进行选择,可以使用 My 功能添加串口名称,确保每个串口都是本机存在的端口。

 

    Public Sub AddComboBoxPorts(ByRef comb As ComboBox)

        comb.Items.Clear()

 

        For Each strPort As String In My.Computer.Ports.SerialPortNames

            comb.Items.Add(strPort)

        Next

    End Sub

 

在打开串口时,一般需要对串口状态进行判断,而且,软件打开串口后,硬件一般有一定的延迟。根据实际的工程项目经验,一般软件打开串口50毫秒后,可以确保硬件工作。打开串口通过函数 OpenPort 进行,其中,comPort 参数通过传址传递,延迟 nDelay 是一个可选参数,缺省为 50 毫秒。

 

    Public Function OpenPort(ByRef comPort As SerialPort, Optional ByVal nDelay _

                            As Integer = 50) As Integer

        '打开串口,并根据需要延迟 nDelay 毫秒

        If comPort.IsOpen = False Then

            Try

                comPort.Open()

                Return 0

            Catch ex As Exception

                Return -1

            End Try

            Threading.Thread.Sleep(nDelay)

        End If

    End Function

 

关闭串口比较简单,通过 ClosePort 函数实现。

 

    Public Sub ClosePort(ByRef comPort As SerialPort)

        '关闭串口

        If comPort.IsOpen = True Then comPort.Close()

    End Sub

 

通过串口发送数据由 SendData 函数来完成。首先调用 GetFullPackage 函数生成16进制字符串形式的综合数据包,然后通过 HexCharsToBytes 函数转换为对应的16进制字节序列,最后调用 SerialPort 类的 Write 方法发送这些字节序列,并返回16进制字符串。

 

    Public Function SendData(ByRef comPort As SerialPort, _

                          ByVal strHexData As String, _

                          ByVal nParity As CheckMode, _

                          ByVal nEnd As EndMark) As String

        Dim strTmp As String

 

        With comPort

            If .IsOpen = False Then Return Nothing

 

            strTmp = GetFullPackage(strHexData, nParity, nEnd)

            Dim outBytes As Byte() = HexCharsToBytes(strTmp)

            .Write(outBytes, 0, outBytes.Length)

        End With

 

        Return strTmp

    End Function

 

当串口有数据时,就调用 ReadData 函数读取,该函数以 SerialPort 对象为参数。调用 SerialPort 对象的 Read 方法,根据字节数 BytesToRead 属性,读取相应的字节,然后转换为16进制字符串返回。

 

    Public Function ReadData(ByRef comPort As SerialPort) As String

        Dim nLength As Integer

        Dim nInBuffer As Byte()

 

        With comPort

            nLength = .BytesToRead

            If nLength < 1 Then Return ""

 

            ReDim nInBuffer(nLength - 1)

            .Read(nInBuffer, 0, nLength)

        End With

 

        Return BytesToHexChars(nInBuffer)

    End Function

 

4.数据接收技术

ReadData 函数可以方便地读取串口数据,但是什么时候读取,获得数据以后又如何进一步处理?这里依然有好多工作要做,这也是串行通信的重点和难点。

一般在 DataReceived 事件处理程序中读取数据,但是,却不能将这些数据放到主界面的文本框中,因为“线程间操作无效”。这可以通过代理来进行,即在事件处理程序中调用代理子程序,在代理子程序中可以操作主界面中的控件。

串行通信发送和接收的数据并不是非常流畅的,中间一般有时间间隔,因而,需要设置一个间隔时间,比如50毫秒,凡是间隔时间在50毫秒之内的数据,都当作一批数据,不断进行汇总,当数据到达的间隔时间超过50毫秒时,再提交这一批数据。

在主窗体的类中作如下声明,strReceiveHex 保存接收到的16进制字符串。bStart True 表示串口正在接收数据,为 False 表示串口处于空闲状态。

 

    Dim strReceiveHex As String = ""

    Dim bStart As Boolean = False

 

在窗体类中定义如下代理及其实体函数,每次 DataReceived 事件都调用一次 GeneralCom 函数,Timer_Port 是一个定时器,用来规定串行通信的数据间隔时间。当有数据到达时,即复位定时器,接收完数据后,重新开始计时。在该函数中,每次接收到的数据都要汇总到 strReceiveHex 变量中。

 

    Public Delegate Sub DelegateCom()

    Dim dCom As DelegateCom = New DelegateCom(AddressOf GeneralCom)

    Private Sub GeneralCom()

        Dim strTmpHex As String = ""    保存一次接收的数据

 

        Timer_Port.Enabled = False  定时器复位,开始处理数据

 

        If bStart = False Then     

            strReceiveHex = ""      清空,准备保存数据

            bStart = True           现在串口处于接收数据状态

        End If

 

        strTmpHex = ReadData(comPort)  读取数据

        strReceiveHex &= strTmpHex     汇总数据

 

        Timer_Port.Enabled = True   本次数据接收完毕,开始计时

    End Sub

 

SerialPort 对象的DataReceived 事件处理程序中,只要书写一条语句即可。

Me.Invoke(dCom)

类似地,处理 SerialPort 对象的PinChanged ErrorReceived 事件要简单得多,这里不再赘述。

如果在规定的时间间隔内没有数据到达,则认为一批数据已经接收完毕。这时,可以在 Timer_Port Tick 事件处理程序中提交数据,或者进行进一步的处理。首先,禁止定时器工作,然后,将 bStart 变量设置为 False,表示数据接收完毕,现在串口处于空闲状态,最后,将汇总后的16进制字符串 strReceiveHex 放入文本框中显示(或做其它处理)。

 

        Timer_Port.Enabled = False

        bStart = False

        txtReceive.Text = strReceiveHex

  推荐精品文章

·2024年12月目录 
·2024年11月目录 
·2024年10月目录 
·2024年9月目录 
·2024年8月目录 
·2024年7月目录 
·2024年6月目录 
·2024年5月目录 
·2024年4月目录 
·2024年3月目录 
·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089