RS232-485串行接口是一種非常成熟的通信接口,曾幾何時(shí),我們用的鼠標(biāo)是串口的,Modem是串口的,還有早期的一些數(shù)碼相機(jī)都是串口的,時(shí)過(guò)境遷,家用電腦現(xiàn)在已是USB時(shí)代,串口這種東西逐漸淡出了我們的視線(xiàn)。 但是,在工業(yè)控制上,串行接口依然有著不可替代的優(yōu)勢(shì),首先是電氣連接簡(jiǎn)單,雖說(shuō)速率不高,但抗干擾能力強(qiáng),通訊距離很遠(yuǎn),甚至可以鋪設(shè)幾百米的電纜,這些都是USB不能取代的。
對(duì)串行接口的操作,微軟公司很早前就提供了一個(gè)通用控件,她就是大名鼎鼎的MSCOMM,這個(gè)控件可以嵌入幾乎所有宿主語(yǔ)言,包括主流的VC VB DELPHI C++ Build等等。通過(guò)這個(gè)控件,我們可以極其輕易地對(duì)串口進(jìn)行操作。但是,這個(gè)控件依然不是完美的,因?yàn)槲④浽趯?xiě)這個(gè)控件的時(shí)候,考慮的都是一般性的常規(guī)的操作,不過(guò),一旦遇到非常規(guī)操作,控件立刻就顯示出它的局限性,正如可視化編程下控件濫用的壞習(xí)慣一樣,沒(méi)有人再去花心思研究程序的內(nèi)部原理,鼠標(biāo)拖一下,鍵盤(pán)敲幾個(gè)調(diào)用,甚至一個(gè)程序就出來(lái)了,這并不是好事,一旦遇到非常規(guī)事務(wù),立刻就會(huì)束手無(wú)策。
把話(huà)題拉回串行接口,串型數(shù)據(jù)RS232接口的基本概念是以高低脈沖來(lái)區(qū)分0或者1,以一個(gè)字節(jié)(Byte)為最小單位進(jìn)行發(fā)送,一個(gè)Byte為8個(gè)二進(jìn)制位(BIT),另外附加三個(gè)位作為起始位、停止位和奇偶校驗(yàn)位。在選擇不使用奇偶校驗(yàn)的情況下,串口一次最小傳送10個(gè)BIT,如果需要奇偶校驗(yàn),則是11個(gè)BIT,排列如下:
[起始位] [數(shù)據(jù)位1到8] [奇偶校驗(yàn)位] [停止位]
奇偶校驗(yàn)的原理是,計(jì)算數(shù)據(jù)位內(nèi)上升沿的個(gè)數(shù),也就是BIT=1的次數(shù),然后再根據(jù)這個(gè)個(gè)數(shù)據(jù)決定奇偶校驗(yàn)位是0還是1,比如說(shuō)發(fā)送1這個(gè)數(shù),并且現(xiàn)在我們選用奇校驗(yàn),則奇偶校驗(yàn)位是0,因?yàn)?原始數(shù)據(jù)=1,奇偶校驗(yàn)位=0,1+0=1),1是奇數(shù)。 如果選用偶校驗(yàn),則奇偶校驗(yàn)位會(huì)自動(dòng)變成1,(原始數(shù)據(jù)=1,奇偶校驗(yàn)位=1,1+1=2),2是偶數(shù)。發(fā)送方將數(shù)據(jù)和奇偶校驗(yàn)位一起發(fā)送,接受方開(kāi)始接收數(shù)據(jù),并且核對(duì)奇偶校驗(yàn)位,一旦發(fā)現(xiàn)奇偶校驗(yàn)位有誤,則立刻報(bào)錯(cuò),因?yàn)檫@說(shuō)明數(shù)據(jù)傳輸受到了干擾。
奇偶校驗(yàn)位一般被稱(chēng)為串口的“第九位”,這個(gè)位其實(shí)除了校驗(yàn)數(shù)據(jù)外,還有別的另類(lèi)玩法。在主機(jī)上利用串行接口對(duì)多設(shè)備進(jìn)行控制的時(shí)候,主機(jī)發(fā)送到每一條命令,必須要編上地址才行,否則就變成廣播操作了,就像老大一聲吼,底下的小弟們?nèi)空癖劭窈簦@在某些時(shí)候確實(shí)有用,但如果老大只點(diǎn)了一個(gè)小弟的名字讓他單獨(dú)回答,就會(huì)出問(wèn)題了,人類(lèi)于是有了名字,而在工業(yè)控制上,模塊都需要編上地址,這跟名字其實(shí)沒(méi)什么本質(zhì)上的區(qū)別。串行數(shù)據(jù)流里面,往往利用第九位來(lái)區(qū)分是地址包還是數(shù)據(jù)包,大家約定,凡是第九位為1的BYTE,說(shuō)明這是地址,凡是第九位為0的BYTE,那是數(shù)據(jù)。主機(jī)控制下的各分機(jī)只有在接受到第九位為1的時(shí)候,才進(jìn)行地址識(shí)別,如果確實(shí)與主機(jī)呼叫的地址一致,才開(kāi)始識(shí)別接下來(lái)的數(shù)據(jù)(第九位為0)。可以看出,這樣的方式是很聰明的,各分機(jī)沒(méi)有必要頻繁地接收主機(jī)發(fā)送到數(shù)據(jù)流,只有收到第九位為1并且符合自己地址之后,才進(jìn)行接收,效率不言而喻。
如果采用第九位作為地址/數(shù)據(jù)的區(qū)分,那么串口將喪失奇偶校驗(yàn)功能,這是沒(méi)有辦法的事,魚(yú)與熊掌不可兼得嘛。所以在Windows串行接口規(guī)范里,對(duì)這個(gè)位有5種設(shè)置,分別是:
NOPARITY = 無(wú)校驗(yàn)
ODDPARITY = 偶校驗(yàn)
EVENPARITY = 奇校驗(yàn)
MARKPARITY = 第九位強(qiáng)設(shè)為1
SPACEPARITY = 第九位強(qiáng)設(shè)為0
在發(fā)地址包的時(shí)候, 可以把Parity設(shè)置成MARKPARITY. 則第九位常為1.
在發(fā)數(shù)據(jù)包的時(shí)候, 可以把Parity設(shè)置成SPACEPARITY.則第九位常為0.
看起來(lái)不困難,無(wú)非就是改變第九位的狀態(tài)而已嘛。但是,很快,可怕的事情來(lái)了,使用MSCOMM控件的話(huà),如果頻繁地改動(dòng)奇偶校驗(yàn)操作,則通訊將會(huì)出現(xiàn)丟包等莫名其妙的問(wèn)題!但我們?yōu)榱藚^(qū)分?jǐn)?shù)據(jù)和地址,這種頻繁改動(dòng)又是必須的,怎么辦?只能扔掉MSCOMM,另尋他途了。
利用API搭建一個(gè)串口通訊程序,是一個(gè)好辦法,API程序直接作用于Windows,效率很高,VC++用的類(lèi)庫(kù)MFC無(wú)非也就是將成千上萬(wàn)的API函數(shù)集中起來(lái)并加以聚合,抽象。現(xiàn)在我們直接使用API,當(dāng)然是可行的,但是,因?yàn)閂isual Basic本身的缺陷,她沒(méi)辦法像VC那樣創(chuàng)建多線(xiàn)程程序(至少實(shí)現(xiàn)起來(lái)極其困難),在以下的例子里我們只能采用同步的方法來(lái)獲得串口的數(shù)據(jù)而不能實(shí)現(xiàn)異步接收,等等,到底什么叫同步?異步?簡(jiǎn)單地說(shuō),比如你拖一個(gè)1G的文件從C盤(pán)到D盤(pán),這需要大量的時(shí)間,如果這段時(shí)間系統(tǒng)一直等著它完成COPY的操作,其他什么都不管理,那么這就叫同步(回憶一下DOS時(shí)代不就是這樣的嗎)。但是,如果系統(tǒng)只是給它這么一條指令,然后你該什么時(shí)候COPY完后通知我一聲,讓我知道你COPY完了就行了,系統(tǒng)在這段時(shí)間內(nèi)不會(huì)死等這個(gè)操作完成,而是釋放開(kāi)給別的有需要的程序(在Windows時(shí)代,你可以邊COPY邊聽(tīng)歌),這就叫異步。很顯然,異步操作聰明得多,也比較合理,最大的優(yōu)勢(shì)是榨干了CPU的效能,但鑒于VB這方面完全不行,所以也只好采用同步的方法了。
以下是源代碼:
API聲明:
Option Explicit
'奇偶校驗(yàn)常數(shù)
Public Const NOPARITY = 0
Public Const ODDPARITY = 1
Public Const EVENPARITY = 2
Public Const MARKPARITY = 3
Public Const SPACEPARITY = 4
'-------------------------------------------------------------------------------
' 文件操作常數(shù)
'-------------------------------------------------------------------------------
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
' 通訊常數(shù)
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
'-------------------------------------------------------------------------------
'通訊結(jié)構(gòu)
'-------------------------------------------------------------------------------
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結(jié)構(gòu),用于串口的設(shè)置
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函數(shù)的聲明:
'建立通訊連接
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
'關(guān)閉通訊連接
Public Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
'發(fā)送數(shù)據(jù)
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
'讀取數(shù)據(jù)
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串口設(shè)置狀態(tài)
Public Declare Function GetCommState Lib "kernel32" (ByVal nCid As Long, lpDCB As DCB) As Long
'構(gòu)建DCB串口設(shè)置狀態(tài)
Public Declare Function BuildCommDCB Lib "kernel32" Alias "BuildCommDCBA" (ByVal lpDef As String, lpDCB As DCB) As Long
'設(shè)置DCB串口設(shè)置狀態(tài)
Public Declare Function SetCommState Lib "kernel32" (ByVal hCommDev As Long, lpDCB As DCB) As Long
'設(shè)置串口的緩沖區(qū)
Public Declare Function SetupComm Lib "kernel32" (ByVal hFile As Long, ByVal dwInQueue As Long, ByVal dwOutQueue As Long) As Long
'清除串口緩沖區(qū)的數(shù)據(jù)
Public Declare Function PurgeComm Lib "kernel32" (ByVal hFile As Long, ByVal dwFlags As Long) As Long
'設(shè)置串口的超時(shí)狀態(tài)
Public Declare Function SetCommTimeouts Lib "kernel32" (ByVal hFile As Long, lpCommTimeouts As COMMTIMEOUTS) As Long
'獲取錯(cuò)誤狀態(tài)
Public Declare Function GetLastError Lib "kernel32" () As Long
'產(chǎn)生一個(gè)系統(tǒng)延時(shí),單位毫秒
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 '定義串口狀態(tài)結(jié)構(gòu)
Dim lngError As Long '定義串口狀態(tài)錯(cuò)誤
Dim flag As Long '定義回傳值
Dim typDCB As DCB '定義DCB串口設(shè)置塊
Dim strSettings As String
flag = SetupComm(hCF, 1024, 1024) '設(shè)置緩沖區(qū)大小,1K
'強(qiáng)制清空讀寫(xiě)緩沖區(qū)
flag = PurgeComm(hCF, PURGE_RXABORT Or PURGE_RXCLEAR Or PURGE_TXABORT Or PURGE_TXCLEAR)
'定義超時(shí)結(jié)構(gòu)體
Dim typCommTimeouts As COMMTIMEOUTS
typCommTimeouts.ReadIntervalTimeout = 0 '相鄰兩字節(jié)讀取最大時(shí)間間隔(為0表示不使用該超時(shí)間隔)
typCommTimeouts.ReadTotalTimeoutMultiplier = 10 '一個(gè)讀操作的時(shí)間常數(shù)
typCommTimeouts.ReadTotalTimeoutConstant = 10 '讀超時(shí)常數(shù)
typCommTimeouts.WriteTotalTimeoutMultiplier = 0 '一個(gè)寫(xiě)操作的時(shí)間常數(shù)(為0表示不使用該超時(shí)間隔)
typCommTimeouts.WriteTotalTimeoutConstant = 0 '寫(xiě)超時(shí)常數(shù)(為0表示不使用該超時(shí)間隔)
'超時(shí)設(shè)置
flag = SetCommTimeouts(hCF, typCommTimeouts)
Dim addressByte(0 To 1) As Byte '地址位,兩個(gè)字節(jié)
Dim dataByte(0 To 3) As Byte '數(shù)據(jù)位,四個(gè)字節(jié)
flag = GetCommState(hCF, typDCB)
strSettings = "baud=19200 parity=m data=8 stop=1" '首先將奇偶校驗(yàn)位調(diào)節(jié)到M模式,則強(qiáng)制設(shè)為1
flag = BuildCommDCB(strSettings, typDCB) '構(gòu)建DCB塊
flag = SetCommState(hCF, typDCB) '設(shè)置DCB塊
addressByte(0) = &H0 '分機(jī)編號(hào)0000,占用兩個(gè)字節(jié)
addressByte(1) = &H0
Ret = WriteFile(hCF, addressByte(0), 2, flag, ByVal 0&) '發(fā)送
flag = GetCommState(hCF, typDCB)
strSettings = "baud=19200 parity=s data=8 stop=1" '首先將奇偶校驗(yàn)位調(diào)節(jié)到S模式,則強(qiáng)制設(shè)為0
flag = BuildCommDCB(strSettings, typDCB)
flag = SetCommState(hCF, typDCB)
flag = GetCommState(hCF, typDCB)
dataByte(0) = &H3 '這是數(shù)據(jù),我的數(shù)據(jù)為4個(gè)字節(jié),這個(gè)依據(jù)實(shí)際情況自行定義
dataByte(1) = &H20
dataByte(2) = &H0
dataByte(3) = &H23
Ret = WriteFile(hCF, dataByte(0), 4, flag, ByVal 0&) '發(fā)送
Sleep 50 '延時(shí)50毫秒
'同步接收來(lái)自串口的數(shù)據(jù),數(shù)據(jù)存到Buffer數(shù)組里,我這里取30字節(jié),這個(gè)可以按實(shí)際情況自定
Ret = ReadFile(hCF, Buffer(0), 30, 0, 0)
For I = 0 To 30
Debug.Print Hex(Buffer(I)) '在DEBUG窗口顯示接收過(guò)來(lái)的數(shù)據(jù)
Next I
End Sub
Private Sub Form_Unload(Cancel As Integer)
CloseHandle hCF '關(guān)閉通訊連接
End Sub