1. 程式人生 > >VB下對序列介面第9位的操作以及API實現方法

VB下對序列介面第9位的操作以及API實現方法

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