产品
有人云 — 物联网云平台 零编程,组态拖拽即可实现远程监控。同时支持私有部署,拥有属于自己的软硬件一体化方案
200
+
私有云项目
数据本地化
业务个性化
10
+
万 公有云注册用户
80%公有云功能免费
付费VIP享自定义品牌服务
有人云官网
有人智造 — ODM/EMS 依托自动化、智能化、柔性化、透明化的智能工厂,
提供代研、代料、代工一站式OEM/ODM服务
有人智造官网
有人智能 — 解决方案 打包提供云、管、边、端整体解决方案
从问题诊断、方案设计,到实施落地、系统运维
有人智能官网
新闻资讯
VB下对串行接口第9位的操作以及API实现方法
2010-09-15


RS232-485串行接口是一种非常成熟的通信接口,曾几何时,我们用的鼠标是串口的,Modem是串口的,还有早期的一些数码相机都是串口的,时过境迁,家用电脑现在已是USB时代,串口这种东西逐渐淡出了我们的视线。 但是,在工业控制上,串行接口依然有着不可替代的优势,首先是电气连接简单,虽说速率不高,但抗干扰能力强,通讯距离很远,甚至可以铺设几百米的电缆,这些都是USB不能取代的。

对串行接口的操作,微软公司很早前就提供了一个通用控件,她就是大名鼎鼎的MSCOMM,这个控件可以嵌入几乎所有宿主语言,包括主流的VC VB DELPHI C++ Build等等。通过这个控件,我们可以极其轻易地对串口进行操作。但是,这个控件依然不是完美的,因为微软在写这个控件的时候,考虑的都是一般性的常规的操作,不过,一旦遇到非常规操作,控件立刻就显示出它的局限性,正如可视化编程下控件滥用的坏习惯一样,没有人再去花心思研究程序的内部原理,鼠标拖一下,键盘敲几个调用,甚至一个程序就出来了,这并不是好事,一旦遇到非常规事务,立刻就会束手无策。

把话题拉回串行接口,串型数据RS232接口的基本概念是以高低脉冲来区分0或者1,以一个字节(Byte)为最小单位进行发送,一个Byte为8个二进制位(BIT),另外附加三个位作为起始位、停止位和奇偶校验位。在选择不使用奇偶校验的情况下,串口一次最小传送10个BIT,如果需要奇偶校验,则是11个BIT,排列如下:

[起始位] [数据位1到8] [奇偶校验位] [停止位]


奇偶校验的原理是,计算数据位内上升沿的个数,也就是BIT=1的次数,然后再根据这个个数据决定奇偶校验位是0还是1,比如说发送1这个数,并且现在我们选用奇校验,则奇偶校验位是0,因为(原始数据=1,奇偶校验位=0,1+0=1),1是奇数。 如果选用偶校验,则奇偶校验位会自动变成1,(原始数据=1,奇偶校验位=1,1+1=2),2是偶数。发送方将数据和奇偶校验位一起发送,接受方开始接收数据,并且核对奇偶校验位,一旦发现奇偶校验位有误,则立刻报错,因为这说明数据传输受到了干扰。

奇偶校验位一般被称为串口的“第九位”,这个位其实除了校验数据外,还有别的另类玩法。在主机上利用串行接口对多设备进行控制的时候,主机发送到每一条命令,必须要编上地址才行,否则就变成广播操作了,就像老大一声吼,底下的小弟们全部振臂狂呼,这在某些时候确实有用,但如果老大只点了一个小弟的名字让他单独回答,就会出问题了,人类于是有了名字,而在工业控制上,模块都需要编上地址,这跟名字其实没什么本质上的区别。串行数据流里面,往往利用第九位来区分是地址包还是数据包,大家约定,凡是第九位为1的BYTE,说明这是地址,凡是第九位为0的BYTE,那是数据。主机控制下的各分机只有在接受到第九位为1的时候,才进行地址识别,如果确实与主机呼叫的地址一致,才开始识别接下来的数据(第九位为0)。可以看出,这样的方式是很聪明的,各分机没有必要频繁地接收主机发送到数据流,只有收到第九位为1并且符合自己地址之后,才进行接收,效率不言而喻。

如果采用第九位作为地址/数据的区分,那么串口将丧失奇偶校验功能,这是没有办法的事,鱼与熊掌不可兼得嘛。所以在Windows串行接口规范里,对这个位有5种设置,分别是:
NOPARITY = 无校验
ODDPARITY = 偶校验
EVENPARITY = 奇校验
MARKPARITY = 第九位强设为1
SPACEPARITY = 第九位强设为0

在发地址包的时候, 可以把Parity设置成MARKPARITY. 则第九位常为1.
在发数据包的时候, 可以把Parity设置成SPACEPARITY.则第九位常为0.


看起来不困难,无非就是改变第九位的状态而已嘛。但是,很快,可怕的事情来了,使用MSCOMM控件的话,如果频繁地改动奇偶校验操作,则通讯将会出现丢包等莫名其妙的问题!但我们为了区分数据和地址,这种频繁改动又是必须的,怎么办?只能扔掉MSCOMM,另寻他途了。


利用API搭建一个串口通讯程序,是一个好办法,API程序直接作用于Windows,效率很高,VC++用的类库MFC无非也就是将成千上万的API函数集中起来并加以聚合,抽象。现在我们直接使用API,当然是可行的,但是,因为Visual Basic本身的缺陷,她没办法像VC那样创建多线程程序(至少实现起来极其困难),在以下的例子里我们只能采用同步的方法来获得串口的数据而不能实现异步接收,等等,到底什么叫同步?异步?简单地说,比如你拖一个1G的文件从C盘到D盘,这需要大量的时间,如果这段时间系统一直等着它完成COPY的操作,其他什么都不管理,那么这就叫同步(回忆一下DOS时代不就是这样的吗)。但是,如果系统只是给它这么一条指令,然后你该什么时候COPY完后通知我一声,让我知道你COPY完了就行了,系统在这段时间内不会死等这个操作完成,而是释放开给别的有需要的程序(在Windows时代,你可以边COPY边听歌),这就叫异步。很显然,异步操作聪明得多,也比较合理,最大的优势是榨干了CPU的效能,但鉴于VB这方面完全不行,所以也只好采用同步的方法了。


以下是源代码:
API声明:

Option Explicit

'奇偶校验常数
Public Const NOPARITY = 0
Public Const ODDPARITY = 1
Public Const EVENPARITY = 2
Public Const MARKPARITY = 3
Public Const SPACEPARITY = 4
'-------------------------------------------------------------------------------
' 文件操作常数
'-------------------------------------------------------------------------------
Public Const ERROR_IO_INCOMPLETE = 996&
Public Const ERROR_IO_PENDING = 997
Public Const GENERIC_READ = &H80000000
Public Const GENERIC_WRITE = &H40000000
Public Const FILE_ATTRIBUTE_NORMAL = &H80
Public Const FILE_FLAG_OVERLAPPED = &H40000000
Public Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000
Public Const OPEN_EXISTING = 3


' 通讯常数
Public Const MS_CTS_ON = &H10&
Public Const MS_DSR_ON = &H20&
Public Const MS_RING_ON = &H40&
Public Const MS_RLSD_ON = &H80&
Public Const PURGE_RXABORT = &H2
Public Const PURGE_RXCLEAR = &H8
Public Const PURGE_TXABORT = &H1
Public Const PURGE_TXCLEAR = &H4


'-------------------------------------------------------------------------------
'通讯结构
'-------------------------------------------------------------------------------
Public Type COMSTAT
fBitFields As Long ' See Comment in Win32API.Txt
cbInQue As Long
cbOutQue As Long
End Type


Public Type COMMTIMEOUTS
ReadIntervalTimeout As Long
ReadTotalTimeoutMultiplier As Long
ReadTotalTimeoutConstant As Long
WriteTotalTimeoutMultiplier As Long
WriteTotalTimeoutConstant As Long
End Type


'
'DCB结构,用于串口的设置


Public Type DCB
DCBlength As Long
BaudRate As Long
fBitFields As Long
wReserved As Integer
XonLim As Integer
XoffLim As Integer
ByteSize As Byte
Parity As Byte
StopBits As Byte
XonChar As Byte
XoffChar As Byte
ErrorChar As Byte
EofChar As Byte
EvtChar As Byte
wReserved1 As Integer 'Reserved; Do Not Use
End Type


'各种API函数的声明:
'建立通讯连接
Public Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
'关闭通讯连接
Public Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
'发送数据
Public Declare Function WriteFile Lib "kernel32" (ByVal hFile As Long, lpBuffer As Any, ByVal nNumberOfBytesToWrite As Long, lpNumberOfBytesWritten As Long, lpOverlapped As Long) As Long
'读取数据
Public Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, ByVal lpOverlapped As Long) As Long
'获取DCB串口设置状态
Public Declare Function GetCommState Lib "kernel32" (ByVal nCid As Long, lpDCB As DCB) As Long
'构建DCB串口设置状态
Public Declare Function BuildCommDCB Lib "kernel32" Alias "BuildCommDCBA" (ByVal lpDef As String, lpDCB As DCB) As Long
'设置DCB串口设置状态
Public Declare Function SetCommState Lib "kernel32" (ByVal hCommDev As Long, lpDCB As DCB) As Long
'设置串口的缓冲区
Public Declare Function SetupComm Lib "kernel32" (ByVal hFile As Long, ByVal dwInQueue As Long, ByVal dwOutQueue As Long) As Long
'清除串口缓冲区的数据
Public Declare Function PurgeComm Lib "kernel32" (ByVal hFile As Long, ByVal dwFlags As Long) As Long
'设置串口的超时状态
Public Declare Function SetCommTimeouts Lib "kernel32" (ByVal hFile As Long, lpCommTimeouts As COMMTIMEOUTS) As Long
'获取错误状态
Public Declare Function GetLastError Lib "kernel32" () As Long
'产生一个系统延时,单位毫秒
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)


============================================================

以下是程序代码:

'全局变量hCF为通讯句柄
Dim hCF As Long

Private Sub Form_Load()
'建立通讯连接
hCF = CreateFile("COM1", _
GENERIC_READ Or GENERIC_WRITE, 0, ByVal 0&, _
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)


End Sub



Private Sub Command1_click()
Dim Ret As Long
Dim Buffer(30) As Byte
Dim I As Long


Dim typCommStat As COMSTAT '定义串口状态结构
Dim lngError As Long '定义串口状态错误
Dim flag As Long '定义回传值
Dim typDCB As DCB '定义DCB串口设置块
Dim strSettings As String

flag = SetupComm(hCF, 1024, 1024) '设置缓冲区大小,1K

'强制清空读写缓冲区
flag = PurgeComm(hCF, PURGE_RXABORT Or PURGE_RXCLEAR Or PURGE_TXABORT Or PURGE_TXCLEAR)

'定义超时结构体
Dim typCommTimeouts As COMMTIMEOUTS
typCommTimeouts.ReadIntervalTimeout = 0 '相邻两字节读取最大时间间隔(为0表示不使用该超时间隔)
typCommTimeouts.ReadTotalTimeoutMultiplier = 10 '一个读操作的时间常数
typCommTimeouts.ReadTotalTimeoutConstant = 10 '读超时常数
typCommTimeouts.WriteTotalTimeoutMultiplier = 0 '一个写操作的时间常数(为0表示不使用该超时间隔)
typCommTimeouts.WriteTotalTimeoutConstant = 0 '写超时常数(为0表示不使用该超时间隔)
'超时设置
flag = SetCommTimeouts(hCF, typCommTimeouts)


Dim addressByte(0 To 1) As Byte '地址位,两个字节
Dim dataByte(0 To 3) As Byte '数据位,四个字节

flag = GetCommState(hCF, typDCB)
strSettings = "baud=19200 parity=m data=8 stop=1" '首先将奇偶校验位调节到M模式,则强制设为1
flag = BuildCommDCB(strSettings, typDCB) '构建DCB块
flag = SetCommState(hCF, typDCB) '设置DCB块

addressByte(0) = &H0 '分机编号0000,占用两个字节
addressByte(1) = &H0
Ret = WriteFile(hCF, addressByte(0), 2, flag, ByVal 0&) '发送

flag = GetCommState(hCF, typDCB)
strSettings = "baud=19200 parity=s data=8 stop=1" '首先将奇偶校验位调节到S模式,则强制设为0
flag = BuildCommDCB(strSettings, typDCB)
flag = SetCommState(hCF, typDCB)
flag = GetCommState(hCF, typDCB)

dataByte(0) = &H3 '这是数据,我的数据为4个字节,这个依据实际情况自行定义
dataByte(1) = &H20
dataByte(2) = &H0
dataByte(3) = &H23
Ret = WriteFile(hCF, dataByte(0), 4, flag, ByVal 0&) '发送

Sleep 50 '延时50毫秒


'同步接收来自串口的数据,数据存到Buffer数组里,我这里取30字节,这个可以按实际情况自定
Ret = ReadFile(hCF, Buffer(0), 30, 0, 0)


For I = 0 To 30
Debug.Print Hex(Buffer(I)) '在DEBUG窗口显示接收过来的数据
Next I

End Sub

Private Sub Form_Unload(Cancel As Integer)
CloseHandle hCF '关闭通讯连接
End Sub


关注有人微信公众号
了解更多信息