串口通信的基本概念:
1.在計算機上進行數據的通信有兩種方式。串行方式和並行方式。也就是串口通信和並行通信。即串口通信是計算機傳輸數據的一種通信方式。
2.並行通信以字節為但是進行傳輸數據,相比於串口通信,他的速度快,傳輸距離近。串口通信以比特位傳輸數據,相比於並行通信,他的傳輸速度慢,但是傳輸距離遠。並且串口通信是異步通信,因此,端口可以在一根線上發送數據的同時在另一根線上接收數據
3.串口通信最重要的參數是波特率、數據位、停止位和奇偶校驗。對於兩個進行通信的端口,這些參數必須匹配。
(1)波特率:傳輸速率。如每秒鍾傳送240個字符,而每個字符格式包含10位(1個起始位,1個停止位,8個數據位),這時的波特率為240Bd,比特率為10位*240個/秒=2400bps。
(2)數據位:數據包中發送端想要發送的數據
(3)停止位:用於表示單個包的最后一位,結束標志以及校正時鍾同步
(4)奇偶校驗:檢錯方式。一共有四種檢錯方式:偶、奇、高和低。
4.串口通信的應用場景:串口通信是指外設和計算機間,通過數據線按位進行傳輸數據的一種通訊方式。這種通信方式使用的數據線少,在遠距離通信中可以節約通信成本,但其傳輸速度比並行傳輸低。大多數計算機(不包括筆記本)都包含兩個RS-232串口。串口通信也是儀表儀器設備常用的通信協議。
Windows下串口通信:
1.在windows下,串口是作為文件進行處理。
2.串口通信分為四大步驟:打開串口,關閉串口,配置串口,讀寫串口
(1)打開串口:使用CreateFile函數:
HANDLE WINAPI CreateFile(
_In_ LPCTSTR lpFileName,//要打開或創建的文件名
_In_ DWORD dwDesiredAccess,//訪問類型
_In_ DWORD dwShareMode,//共享方式
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,//安全屬性
_In_ DWORD dwCreationDisposition,//指定要打開的文件已存在或不存在的動作
_In_ DWORD dwFlagsAndAttributes,//文件屬性和標志
_In_opt_ HANDLE hTemplateFile//一個指向模板文件的句柄
);
參數說明:
1).lpFileName:要打開或創建的文件名
2).dwDesiredAccess:訪問類型。0為設備查詢訪問;GENERIC_READ為讀訪問;GENERIC_WRITE為寫訪問;
3).dwShareMode:共享方式。0表示文件不能共享,試圖打開文件的操作都會失敗;FILE_SHARE_READ表示允許其它讀操作;FILE_SHARE_WRITE表示允許寫操作;FILE_SHARE_DELETE表示允許刪除操作。
4).lpSecurityAttributes:安全屬性。一個指向SECURITY_ATTRIBUTES結構的指針。一般傳入NULL
5).dwCreationDisposition:創建或打開文件時的動作。 OPEN_ALWAYS:打開文件,如果文件不存在則創建;TRUNCATE_EXISTING 打開文件,且將文件清空(故需要GENERIC_WRITE權限),若文件不存在則失敗;OPEN_EXISTING打開文件,文件若不存在則會失敗;CREATE_ALWAYS創建文件,如果文件已存在則清空;CREATE_NEW創建文件,如文件存在則會失敗;
6).dwFlagsAndAttributes:文件標志屬性。FILE_ATTRIBUTE_NORMAL常規屬性; FILE_FLAG_OVERLAPPED異步I/O標志,如果不指定此標志則默認為同步IO;FILE_ATTRIBUTE_READONLY文件為只讀; FILE_ATTRIBUTE_HIDDEN文件為隱藏。FILE_FLAG_DELETE_ON_CLOSE所有文件句柄關閉后文件被刪除
7). hTemplateFile:一個文件的句柄,且該文件必須是以GENERIC_READ訪問方式打開的。如果此參數不是NULL,則會使用hTemplateFile關聯的文件的屬性和標志來創建文件。如果是打開一個現有文件,則該參數被忽略。
注意點: 使用該函數打開串口時需要注意的是:文件名直接寫串口號名,如“COM1”,;共享方式應為0,即串口應為獨占方式;打開時的動作應為OPEN_EXISTING,即串口必須存在。
(2)關閉串口:BOOL WINAPI CloseHandle(HANDLE hObject);
(3)配置串口:串口的配置主要包含三部分內容:設置超時和設置緩沖區,設置串口配置信息。
1)超時設置:在讀寫串口的時候,如果沒有指定異步操作,讀寫緩沖區都會一直等待數據達到一定大小才進行讀寫,因此,我們需要設置一個讀寫超時事件以防永遠等待浪費資源。調用SetCommTimeouts()可以設置串口讀寫超時時間,GetCommTimeouts()可以獲得當前的超時設置信息,一般先利用GetCommTimeouts獲得當前超時信息到一個COMMTIMEOUTS結構,然后對這個結構自定義,再調用SetCommTimeouts()進行超時設置:
a.獲取超時信息:
BOOL GetCommTimeouts(
_In_ HANDLE hFile,
_Out_ LPCOMMTIMEOUTS lpCommTimeouts
b.設置超時:
BOOL SetCommTimeouts(
_In_ HANDLE hFile,
_In_ LPCOMMTIMEOUTS lpCommTimeouts
);
c.超時設置信息結構體:
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout; //讀兩個字符間的事件間隔,若兩個字符之間的讀操作間隔超過此間隔則立即返回
DWORD ReadTotalTimeoutMultiplier; //讀操作在讀取每個字符時的超時
DWORD ReadTotalTimeoutConstant; //讀操作的固定超時
DWORD WriteTotalTimeoutMultiplier; //寫操作在寫每個字符時的超時
DWORD WriteTotalTimeoutConstant; //寫操作的固定超時
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
d.隔超時和總超時,間隔超時就是ReadIntervalTimeout,總超時= ReadTotalTimeoutConstant + ReadTotalTimeoutMultiplier*要讀寫的字符數。可以看出:間隔超時和總超時的設置是不相關的,寫操作只支持總超時,而讀操作兩種超時均支持。比如:ReadTotalTimeoutMultiplier設為1000,其余成員為0,如果ReadFile()想要讀取5個字符,則總的超時時間為1*5=5秒;ReadTotalTimeoutConstant設為5000,其余為0,則總的超時時間為5秒;ReadTotalTimeoutMultiplier設為1000並且ReadTotalTimeoutConstant設為5000,其余為0,如果ReadFile()想要讀取5個字符,則總的超時間為1*5+5 =10秒。如果將ReadIntervalTimeout設為MAXDWORD,ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都為0,則讀操作會一次讀入緩沖區的內容后立即返回,不管是否讀入了指定字符。需要注意的是,用重疊方式讀寫串口時,SetCommTimeouts()仍然是起作用的,在這種情況下,超時規定的是I/O操作的完成時間,而不是ReadFile和WriteFile的返回時間。
2).SetupComm()函數用來設置串口的發送/接受緩沖區的大小,如果通信的速率較高,則應該設置較大的緩沖區。
BOOL WINAPI SetupComm(
__in HANDLE hFile,//串口句柄
__in DWORD dwInQueue,//輸入緩沖區大小
__in DWORD dwOutQueue//輸出緩沖區大小
);
3).函數GetCommState()和SetCommState()分別用來獲得和設置串的配置信息,如波特率、校驗方式、數據位個數、停止位個數等。一般也是先調用GetCommState()獲得串口配置信息到一個DCB結構中去,再對這個結構自定義后調用SetCommState()進行設置。
BOOL WINAPI GetCommState(
__in HANDLE hFile,//串口句柄
__out LPDCB lpDCB//保存的串口配置信息
);
BOOL WINAPI SetCommState(
__in HANDLE hFile,//串口句柄
__in LPDCB lpDCB//設置的串口配置信息
);
a.DCB結構中幾個比較重要的成員有:BaudRate(波特率)、fParity(指定奇偶校驗使能)、Parity(校驗方式)、ByteSize(數據位個數)、StopBits(停止位個數)。
b.BaudRate波特率常用的有CBR_9600、CBR_14400、CBR_19200、CBR_38400、CBR_56000、CBR_57600、CBR_115200、 CBR_128000、 CBR_256000。
c.fParity奇偶校驗使能,若為1,則允許奇偶校驗
d.ByteSize數據位個數可以為5~8位。
e.StopBits停止位可以為0~2,對應宏為ONESTOPBIT、ONE5STOPBITS、TWOSTOPBITS,表示1位停止位、1.5位停止位、2位停止位。
(4)讀寫串口:主要包括三部分內容:清空緩沖,清楚錯誤,讀寫串口數據
1).清空緩沖:串口第一次使用或者串口長時間沒用,再次使用時。讀寫串口之前,都需要進行清空緩沖
BOOL PurgeComm(HANDLE hFile, DWORD dwFlags );
第二個參數dwFlags指定串口執行的動作,可以是以下值的組合:
-PURGE_TXABORT:停止目前所有的傳輸工作立即返回不管是否完成傳輸動作。
-PURGE_RXABORT:停止目前所有的讀取工作立即返回不管是否完成讀取動作。
-PURGE_TXCLEAR:清除發送緩沖區的所有數據。
-PURGE_RXCLEAR:清除接收緩沖區的所有數據。
如清除串口的所有操作和緩沖:PurgeComm(hComm, PURGE_RXCLEAR|PURGE_TXCLEAR|PURGE_RXABORT|PURGE_TXABORT);
2).清楚錯誤:清除通信錯誤以及獲取當前通信狀態。在讀寫操作之前,可以調用ClearCommError來清除錯誤和獲得緩沖區內數據大小。
OOL WINAPI ClearCommError(
_In_ HANDLE hFile,//串口句柄
_Out_opt_ LPDWORD lpErrors,//返回的錯誤碼
_Out_opt_ LPCOMSTAT lpStat//返回的通訊狀態
);
pErrors用來保存錯誤碼,具體對應的什么錯誤為:
-CE_BREAK:檢測到中斷信號。意思是說檢測到某個字節數據缺少合法的停止位。
-CE_FRAME:硬件檢測到幀錯誤。
-CE_IOE:通信設備發生輸入/輸出錯誤。
-CE_MODE:設置模式錯誤,或是hFile值錯誤。
-CE_OVERRUN:溢出錯誤,緩沖區容量不足,數據將丟失。
-CE_RXOVER:溢出錯誤。
-CE_RXPARITY:硬件檢查到校驗位錯誤。
-CE_TXFULL:發送緩沖區已滿。
lpStat為指向_COMSTAT結構的指針,保存通訊狀態。一般我們只關心這個結構中的兩個成員:cbInQue、cbOutQue,分別表示輸入緩沖區中的字節數、輸出緩沖區中的字節數。
3).讀寫串口數據:WriteFile()向串口中寫數據,ReadFile()從串口讀數據
BOOL WINAPI ReadFile(
_In_ HANDLE hFile,//文件句柄
_Out_ LPVOID lpBuffer,//指向一個緩沖區,保存讀取的數據
_In_ DWORD nNumberOfBytesToRead,//要讀取數據的字節數,如果實際讀取的字節數小於這個數的話函數會一直等待直到超時
_Out_opt_ LPDWORD lpNumberOfBytesRead,//實際讀取的字節數
_Inout_opt_ LPOVERLAPPED lpOverlapped//指向一個OVERLAPPED結構,用於異步操作
);
BOOL WINAPI WriteFile(
_In_ HANDLE hFile,//文件句柄
_In_ LPCVOID lpBuffer,//指向一個緩沖區,包含要寫入的數據
_In_ DWORD nNumberOfBytesToWrite,//要寫入數據的字節數
_Out_opt_ LPDWORD lpNumberOfBytesWritten,//實際寫入的字節數
_Inout_opt_ LPOVERLAPPED lpOverlapped//指向一個OVERLAPPEN結構體,用於異步操作
);
使用這兩個函數進行串口讀寫時有兩個地方需要注意:
第一點:如果想要異步讀寫操作,則lpOverlappen參數不能為NULL,而且在CreateFile()打開文件時應指定FILE_FLAG_OVERLAPPEN標記。在異步讀寫操作的時候,ReadFile()和WriteFile()返回FALSE時應調用GetLastError函數分析返回的結果,如果是ERROR_IO_PENDING,這說明異步I/O操作正在進行。
第二點:在用ReadFile()讀文件時,如果想要讀取的數據大小比文件內容大,則只會讀取文件大小的數據。而讀串口時,如果想要讀取的數據比緩沖區中數據大,則ReadFile()會阻塞,直到數據到達或者超時。
另外還需要說明的是:函數WriteFileEx()與ReadFileEx()只能用於異步讀寫操作,而且可以設置一個讀寫完成后自動調用的回調函數,函數執行成功返回TRUE,表示異步I/O操作開始,出錯返回FALSE。
3.異步讀寫串口:在串口編程中,可以先設置好串口所關注的事件,然后啟動一個輔助線程來監聽該事件是否已經發生,如果沒有發生的話該線程就一直等待,當事件發生后,如讀緩沖區中收到數據,該線程可以向主線程窗體發送對應事件消息提示進行讀串口處理,或者在輔助線程中直接進行異步讀寫串口處理。
4.監聽串口事件和異步讀寫串口:
(1)設置串口監聽事件SetCommMask函數:
BOOL SetCommMask(HANDLE hFile, DWORD dwEvtMask);
參數:
hFile為串口句柄,
dwEvtMask為要監視的串口事件掩碼,可以有以下位值:
EV_RXCHAR:輸入緩沖區中收到數據
EV_TXEMPTY:輸出緩沖區中的數據已被完全送出
EV_RXFLAG:使用SetCommState()函數設置的DCB結構中的事件字符已被傳入輸入緩沖區中
(2)事件設置好以后可以使用WaitCommEvent()來判斷事件是否已經發生
BOOL WINAPI WaitCommEvent(_In_ HANDLE hFile,_Out_ LPDWORD lpEvtMask,_In_ LPOVERLAPPED lpOverlapped);
參數:
hFile:串口句柄
lpEvtMask:檢測到串口通信事件的話就將其寫入該參數中。
lpOverlapped:指向一個重疊結構,如果串口打開時指定了FILE_FLAG_OVERLAPPED標志 ,則改參數不能為NULL,且重疊結構中 應該包含一個事件對象對象句柄(通過CreateEvent()創建)。
如果不是異步讀寫的話,WaitCommEvent()會一直等待事件的發生,如果異步讀寫沒有立即完成的話函數會直接返回FALSE,調用GetLastError()會返回ERROR_IO_PENDING
目前發現了一個BUG:如果CloseHandle()關閉串口的時候,WaitCommEvent()還在等待事件,那么程序就會出現卡死現象,而且在同步讀寫下很容易發生這種情況。 MSDN上說如果是重疊操作的話再次調用SetCommMask()改變事件掩碼將會使WaitCommEvent()立即返回,但我試了下在同步讀寫情況下這種方法不管用,不知道重疊操作的情況是否真的管用!
5.異步讀寫串口
重疊模型是異步I/O方式中一種,所以可以使用重疊操作來實現異步讀寫串口。前面說過,如果重疊操作不能立即完成,則WaitCommEvent()返回FALSE,GetLastError()會返回ERROR_IO_PENDING,表示操作正在后台進行,在WaitCommEvent返回之前,參數重疊結構中的hEvent成員會被設置為無信號狀態,如果當事件發生或錯誤發生時,其被設置為有信號狀態,應用程序可以調用wait functions(WaitForSingleObject、WaitForSingleObjectEx等)來判斷事件對象的狀態,而WaitCommEvent()的參數lpEvtMask會保存具體發生的事件。
有兩種方法可以等待或者判斷重疊操作是否完成,一種是使用WaitForSingleObject()來等待讀寫函數中OVERLAPPED類型的參數的hEvent成員:當調用ReadFile, WriteFile 函數的時候,該成員會自動被置為無信號狀態;當重疊操作完成后,該成員變量會自動被置為有信號狀態。另一種方法是調用GetOverlappedResult()獲得重疊操作的狀態,來判斷重疊操作是否完成,函數原型:
BOOL WINAPI GetOverlappedResult(HANDLE hFile,//文件句柄
LPOVERLAPPED lpOverlapped,//指向欲檢查的重疊結構
LPDWORD lpNumberOfBytesTransferred,//返回重疊操作(讀或寫)的字節數
BOOL bWait
);
如果參數bWait為TRUE則函數會一直等待直到重疊結構中的hEvent變成有信號,即一直等到重疊操作完成;FALSE為如果檢測到pending狀態則立即返回,此時函數返回FALSE,GetLastError()返回值為ERROR_IO_INCOMPLETE。
