目錄
一串口通信基礎
1.1串口通信原理與特點
1.2串口通信的傳輸方式
1.3串口通信的同步技術
1.4串行接口標准
二 API函數實現串口通信
2.1打開串口
2.1.1串口是否有驅動
2.1.2連接串口
2.1.3串口邏輯端口號大於10無法打開問題
2.2串口配置
2.2.1設置緩沖區大小
2.2.2設置串口狀態
2.2.3設置需通知的事件
2.2.4清空緩沖區
2.3異步接收數據
三示例代碼
3.1連接串口並設置參數
3.2發送與接收數據
3.3關閉串口
一串口通信基礎
提到串口讓人想起並口,它們是計算機中兩個比較重要的通信方式.
串口:也叫COM口,把字節的二進制位按位列隊進行傳輸,每個字節占一個固定的時間長,速度慢,但是傳輸距離遠,有9針和25針兩種,是陽插座(插座中有針凸起),目前25針較少使用;Modem\鼠標\USB口\老式攝像頭等都是用串口.
並口:把字節的二進制位用多條線同時傳輸,速度快串口8倍左右,傳輸距離有限,一般計算機內部數據傳輸用此方式,平常使用的有打印機,掃描儀等;為25針,陰插座(插座有25個針孔).
1.1串口通信原理與特點
串行端口是CPU與串行設備間的編碼轉換器,當CPU經過串行端口發數據時,字節數據列隊成串行位,串行端口接收數據時,串行位轉換成字節數據.所以必須安裝相應的驅動程序.
串行通信有成本低的特點,而且可以在現有的電話網絡上進行傳輸,家庭通過電話線上網即是這種方式.只要配置一個相應的通信接口,如:Modem.
1.2串口通信的傳輸方式
單工:只能從一頭傳輸到另一頭,如只能從A向B傳或者B向A傳,如看電視,只允許電視台向電視發數據,不允許電視向電視台發數據.在單工傳輸方式上一般采用兩個通信,一個通道傳輸數據,一個通道傳輸控制信號.
半雙工:允許互傳信息,但是不能同時進行,如對講機,A說話時,B不能說話,B說話時A不能說話.
全雙工:允許雙同時通信,如講電話.
1.3串口通信的同步技術
物理連接建立后,需要使用一種機制使對方正確解釋發送的數據,發送方安位發出數據后,接收方如何識別這些數據,並如何正確組裝成正確的字節.這就需要同步技術.數據同步技術一般解決如下問題:
² 確定發送數據起始時間
² 發送數據的傳輸速率
² 發送數據所需的時間
² 發送時間間隔
3.1異步傳輸
按字節為單位傳輸,異步傳輸方式也叫起止方式,在被傳輸的字節前后加起止位,起止位無信號時處於高電平,接收方檢測到低電平信號表示開始接收,收到停止信號表示傳輸完成.
3.2同步傳輸
以數據塊為單位傳輸,在塊的前后加一個特殊字節表示起止,傳輸效率高,線路利用率高,設備負擔也大.
1.4串行接口標准
常用標准有RS-232C,RS-485,RS-422等,其中RS-232C被廣泛用於計算機串口通信.RS-232C標准要求一般線路不要超過15米.
二 API函數實現串口通信
API函數串口編程,可采用簡單的查詢方式或定時方式,也可采用復雜的事件驅動方式,所謂事件驅動方式是當輸入緩沖區中有數據時,將自動調用某個方法執行相應的操作.定時方式是在一定的時間間隔內判斷緩沖區內有數據被寫入,此方法效率不高,查詢方式就更落后的一種方式.所以設計的好的串口通信程序一般用事件驅動,有實時,高效,靈活等特點.
一般編制串行通信程序分以下幾個部分:
Ø 打開串行端口:打開通信資源,設置通信參數、設置通信事件、創建讀、寫事件、進入等待串口消息循環。
Ø 讀取串行端口信息:當串口發生EV_RXCHAR(接收到字符並放入了輸入緩沖區)消息后讀取串口、數據傳輸錯誤處理、字符串處理如回車符、空格並相應轉化成數據,如果模擬量還要進行數據檢驗等功能。
Ø 寫串行端口信息:將要發送的信息寫入串口,相應進行錯誤處理。
Ø 斷開串行端口連接:關閉事件,清除通信事件,丟棄通信資源並關閉。
2.1打開串口
2.1.1串口是否有驅動
如何判斷PC機中串口是否正常,驅動是否安裝,串口名(邏輯端口名)是多少.如果PC機有串口同時驅動正常,那么在注冊表的HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP目錄下,包含字符"Serial"或"VCom"項下面的值就是,可以有多項,如下圖:
項SERIALCOMM下有一個值----COM11,表明有一個可用串口,如果目錄下包含字符Serial或VCom的項下沒有任何值,表明沒有串口或者驅動不正常.以下是獲取串口邏輯名的代碼:
#define MAX_KEY_LENGTH 255
#define MAX_VALUE_NAME 16383
HKEY hTestKey;
if(ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DEVICEMAP\\SERIALCOMM"), 0, KEY_READ, &hTestKey) ){
TCHAR achClass[MAX_PATH] = TEXT(""); // buffer for class name
DWORD cchClassName = MAX_PATH; // size of class string
DWORD cSubKeys=0; // number of subkeys
DWORD cbMaxSubKey; // longest subkey size
DWORD cchMaxClass; // longest class string
DWORD cValues; // number of values for key
DWORD cchMaxValue; // longest value name
DWORD cbMaxValueData; // longest value data
DWORD cbSecurityDescriptor; // size of security descriptor
FILETIME ftLastWriteTime; // last write time
DWORD i, retCode;
TCHAR achValue[MAX_VALUE_NAME];
DWORD cchValue = MAX_VALUE_NAME;
// Get the class name and the value count.
retCode = RegQueryInfoKey(
hKey, // key handle
achClass, // buffer for class name
&cchClassName, // size of class string
NULL, // reserved
&cSubKeys, // number of subkeys
&cbMaxSubKey, // longest subkey size
&cchMaxClass, // longest class string
&cValues, // number of values for this key
&cchMaxValue, // longest value name
&cbMaxValueData, // longest value data
&cbSecurityDescriptor, // security descriptor
&ftLastWriteTime); // last write time
if (cValues > 0) {
for (i=0, retCode=ERROR_SUCCESS; i<cValues; i++) {
cchValue = MAX_VALUE_NAME; achValue[0] = '\0';
if (ERROR_SUCCESS == RegEnumValue(hKey, i, achValue, &cchValue, NULL, NULL, NULL, NULL)) {
CString szName(achValue);
if (-1 != szName.Find(_T("Serial")) || -1 != szName.Find(_T("VCom")) ){
BYTE strDSName[10]; memset(strDSName, 0, 10);
DWORD nValueType = 0, nBuffLen = 10;
if (ERROR_SUCCESS == RegQueryValueEx(hKey, (LPCTSTR)achValue, NULL,
&nValueType, strDSName, &nBuffLen)){
int nIndex = -1;
while(++nIndex < 20){
if (-1 == m_nComArray[nIndex]) {
m_nComArray[nIndex] = atoi((char*)(strDSName + 3));
break;
}
}
}
}
}
}
}
else{
AfxMessageBox(_T("機PC機沒有COM口....."));
}
}
RegCloseKey(hTestKey);
2.1.2連接串口
串口是系統資源,也當作文件一樣操作,所以也用CreateFile函數,如果調用成功返回串口句柄,如果失敗返回INVALID_HANDLE_VALUE值.函數參數說明如下:
HANDLE WINAPI CreateFile(
__in LPCTSTR lpFileName,//串口名(邏輯端口名),如:”COM1”,”COM2”
__in DWORD dwDesiredAccess,//訪問模式,對串口有讀/寫權限
__in DWORD dwShareMode,//共享模式,有讀/寫/刪除共享,對串口通信只能獨占模式
__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,//文件安全屬性,對串口設置為NULL
__in DWORD dwCreationDisposition,//創建方式,串口通信設置為OPEN_EXISTING
__in DWORD dwFlagsAndAttributes,//文件屬性標記,是否異步方式,在些設置
__in_opt HANDLE hTemplateFile);//文件句柄,如果不為NULL,新文件從該夠本復制或擴展,如果函數執行成功,返回新的串口句柄.
Ø 第一個參數:邏輯串口號,用字符串”COMX”表示,”X”是串口序號,關於電腦中的邏輯串口號,在注冊表HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP目錄下,包含字符"Serial"或"VCom"項下面的值就是,可以有多個
Ø 第二個參數:是對對串口的訪問權限,串口只有讀/寫(GENERIC_READ/GENERIC_WRITE)權限,可以同時設置成讀/寫權限,也可以單獨只設置讀或寫權限.
Ø 第三個參數:是共享模式,有讀/寫/刪除共享,對串口通信只能獨占模式,即是0.
Ø 第四個參數:文件安全屬性,對串口設置成NULL.
Ø 第五個參數:創建方式,有CREATE_ALWAYS/ CREATE_NEW/ OPEN_EXISTING等方式,但是對串口只能是OPEN_EXISTING,只能打開存在的串口,不像文件一樣可創建之類
Ø 第六個參數:文件屬性與標志,詳細信息查看MSDN,如果想把串口設置成異步方式,那么要設置成FILE_FLAG_OVERLAPPED.
Ø 第七個參數:文件句柄,新文件從該句柄復制或擴展,如果函數執行成功,返回新的句柄,對串口通信,設置成NULL
以下為代碼示例:
HANDLE hCom = CreateFile("COM1", //打開串口1
GENERIC_READ|GENERIC_WRITE, //允許讀和寫操作
0, //獨占方式
NULL,
OPEN_EXISTING, //打開存在的串口,必須是OPEN_EXISTING,文件還可以CREATE_NEW,串口不能創建
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //異步方式打開
NULL);
if (INVALID_HANDLE_VALUE == m_hCom) {
int nError = GetLastError();
}
如果執行成功返回串口句柄,如果失敗通過GetLastError獲取錯誤碼.
2.1.3串口邏輯端口號大於10無法打開問題
通過函數CreateFile打開串口,其第一個參數就是串口的邏輯端口名,是用”COMX”表示的,其中X是1~N的整數,當X大於10時,有時會出現無法打開的問題,把邏輯端口名改成"\\\\.\\COM
X"即可解決.
CString szCom;
szCom.Format(_T("\\\\.\\COM%d"), nPort);
COMFile = CreateFile(szCom.GetBuffer(50), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED,NULL);
2.2串口配置
對串口進行設置,如緩沖區大小,是否允許二進制,參數配置等.
2.2.1設置緩沖區大小
設置IO的緩沖區大小根據自己需求,太小容易丟失數據,所以根據自己的需求設置.
BOOL SetupComm(
HANDLE hFile, //串口句柄,CreateFile的有效返回值
DWORD dwInQueue, //輸入緩沖區字節數
DWORD dwOutQueue);//輸出緩沖區字節數
2.2.2設置串口狀態
設置串口的各種狀態,波特率/數據位/停止位/校驗位/流控制/二進制等.設置窗口狀態是通過函數SetCommState設置的,第一個參數是串口句柄,第二個是DCB指針.DCB結構比較復雜,參數多,所以一般是通過GetCommState來填充一個DCB對象,然后再修改這對象,再把修改好的對像給SetCommState做參數.這是常用的方法.下面是對DCB參數介紹:
typedef struct _DCB { // dcb
DWORD DCBlength; // sizeof(DCB)
DWORD BaudRate; // current baud rate 指定當前的波特率
DWORD fBinary: 1; // binary mode, no EOF check 指定是否允許二進制模式,WINDOWS 95中必須為TRUE
DWORD fParity: 1; // enable parity checking 指定奇偶校驗是否允許
DWORD fOutxCtsFlow:1;// CTS output flow control 指定CTS是否用於檢測發送控制.當為TRUE是CTS為OFF,發送將被掛起
DWORD fOutxDsrFlow:1;// DSR output flow control 指定DSR是否用於檢測發送控制.當為TRUE是DSR為OFF,發送將被掛起
DWORD fDtrControl:2; // DTR flow control type DTR_CONTROL_DISABLE值將DTR置為OFF, DTR_CONTROL_ENABLE值將
//DTR置為ON, DTR_CONTROL_HANDSHAKE允許DTR"握手",
DWORD fDsrSensitivity:1; // DSR sensitivity 當該值為TRUE時DSR為OFF時接收的字節被忽略
DWORD fTXContinueOnXoff:1; // XOFF continues Tx 指定當接收緩沖區已滿,並且驅動程序已經發送出XoffChar字符時發送
//是否停止.TRUE時,在接收緩沖區接收到緩沖區已滿的字節XoffLim且驅動程序已經發送
//出XoffChar字符中止接收字節之后,發送繼續進行。FALSE時,在接收緩沖區接收到代表緩沖
//區已空的字節XonChar且驅動程序已經發送出恢復發送的XonChar之后,發送繼續進行。
DWORD fOutX: 1; // XON/XOFF out flow control TRUE時,接收到XoffChar之后便停止發送.接收到XonChar之后將重新開始
DWORD fInX: 1; // XON/XOFF in flow control TRUE時,接收緩沖區接收到代表緩沖區滿的XoffLim之后,XoffChar發送出
//去.接收緩沖區接收到代表緩沖區空的XonLim之后,XonChar發送出去
DWORD fErrorChar: 1; // enable error replacement 該值為TRUE且fParity為TRUE時,用ErrorChar 成員指定的字符代替奇
//偶校驗錯誤的接收字符
DWORD fNull: 1; // enable null stripping TRUE時,接收時去掉空(0值)字節
DWORD fRtsControl:2; // RTS flow control RTS_CONTROL_DISABLE時,RTS置為OFF RTS_CONTROL_ENABLE時, RTS置為ON RTS_CONTROL_HANDSHAKE時,當接收緩沖區小於半滿時RTS為ON 當接收緩沖區超過四分之三滿時RTS為OFF RTS_CONTROL_TOGGLE時,當接收緩沖區仍有剩余字節時RTS為ON ,否則缺省為OFF
DWORD fAbortOnError:1; // abort reads/writes on error TRUE時,有錯誤發生時中止讀和寫操作
DWORD fDummy2:17; // reserved 未使用
WORD wReserved; // not currently used 未使用,必須為0
WORD XonLim; // transmit XON threshold 指定在XON字符發送這前接收緩沖區中可允許的最小字節數
WORD XoffLim; // transmit XOFF threshold 指定在XOFF字符發送這前接收緩沖區中可允許的最小字節數
BYTE ByteSize; // number of bits/byte, 4-8 指定端口當前使用的數據位
BYTE Parity; // 0-4=no,odd,even,mark,space 指定端口當前使用的奇偶校驗方法,可能為:
//EVENPARITY,MARKPARITY,NOPARITY,ODDPARITY
BYTE StopBits; // 0,1,2 = 1, 1.5, 2 指定端口當前使用的停止位數,可能為:ONESTOPBIT,ONE5STOPBITS,TWOSTOPBITS
char XonChar; // Tx and Rx XON character 指定用於發送和接收字符XON的值
char XoffChar; // Tx and Rx XOFF character 指定用於發送和接收字符XOFF值
char ErrorChar; // error replacement character 本字符用來代替接收到的奇偶校驗發生錯誤時的值
char EofChar; // end of input character 當沒有使用二進制模式時,本字符可用來指示數據的結束
char EvtChar; // received event character 當接收到此字符時,會產生一個事件
WORD wReserved1; // reserved; do not use 未使用
} DCB;
以下為示例代碼:
DCB dcb;
GetCommState(hCom,&dcb);
dcb.BaudRate = 9600;//波特率
dcb.fBinary = TRUE;//是否允許傳二進制
dcb.fParity = TRUE;//是否奇偶校驗
dcb.ByteSize = 8;//數據位
dcb.Parity = ODDPARITY;//奇偶校驗方式
dcb.StopBits = ONESTOPBIT;//停止位
SetCommState(hCom,&dcb);
2.2.3設置需通知的事件
設置你關心的事件,當此事件發生時,將得到事件通知,通過SetCommMask函數設置,SetCommMask函數兩個參數,第一個為串口句柄,第二個為事件,可通過位或的方式指定多個事件,如下:
BOOL WINAPI SetCommMask(
__in HANDLE hFile,
__in DWORD dwEvtMask);
示例代碼:
SetCommMask(m_hCom, EV_RXCHAR ) ;
EV_RXCHAR事件指當輸入緩沖區內有數據時,通過WaitCommEvent函數可獲得通知,其他事件同理,其他事件還有EV_BREAK/EV_CTS/EV_RING等,詳情參看MSDN.
2.2.4清空緩沖區
在接收/發送數據前緩沖區中可能有垃圾數據,或者中途想清空緩沖區數據,可以用以下調用以下函數:
BOOL WINAPI PurgeComm(
__in HANDLE hFile,//串口句柄
__in DWORD dwFlags//清空方式PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR
);
PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_RXCLEAR分別表示:立即中斷寫操作並清空輸出緩沖區|清空輸出緩沖區|立即中斷讀操作並清空輸入緩沖區|清空輸入緩沖區.示例代碼如下:
PurgeComm(m_hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ;
2.3異步接收數據
在2.2.3中通過SetCommMask函數,設置了異步通知事件EV_RXCHAR,所以當輸入緩沖區中有數據時,將會有事件觸發,那怎么獲得事件呢?通過WaitCommEvent函數截取,與WaitForSingleObject原理類似.函數說明如下:
BOOL WINAPI WaitCommEvent(
__in HANDLE hFile,
__out LPDWORD lpEvtMask,
__in LPOVERLAPPED lpOverlapped);
第一個參數是串口句柄,第二個參數是Out型指針,接收事件標志的,第三個參數接收事件信息事件狀態.以下為示例代碼:
HANDLE hEventArr[2];
hEventArr[0] = osRead.hEvent;
hEventArr[1] = *g_OutPutList.GetEvent();
while(1){
DWORD nResutl = WaitForMultipleObjectsEx(2, hEventArr, FALSE, 200, TRUE/*INFINITE*/);
if(0 == nResutl){
if (g_OutPutList.GetCount() > 1000)
g_OutPutList.RemoveAll();
WORD nLen = (WORD)m_nBuffLen + 2;
PBYTE pIn = new BYTE[nLen];
pIn[0] = HIBYTE(nLen);
pIn[1] = LOBYTE(nLen);
memcpy(pIn + 2, m_InPutBuff, m_nBuffLen);
g_InPutList.AddTail(pIn);
m_nBuffLen = 0;
}
else if(1 == nResutl){
PBYTE pOut = (PBYTE)g_OutPutList.RemoveHead();
int nLen = pOut[0] * 0x100 + pOut[1] - 2;
WriteCommBlock(pOut + 2, nLen);
delete[] pOut;
}
DWORD dwEvtMask = 0 ;
WaitCommEvent( COMFile, &dwEvtMask, &ShareEvent);//等̨¨待äy串ä?口¨²事º?件t
if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR) {
ReadCommBlock( );
}
}
三示例代碼
關於本文的代碼下載鏈接:http://download.csdn.net/detail/mingojiang/4425803 VS2010編寫的,編譯通過,是個串口調試工具. 如圖:
3.1連接串口並設置參數
DCB dcb ;
BOOL fRetVal ;
COMMTIMEOUTS CommTimeOuts;
CString szCom;
szCom.Format(_T("\\\\.\\COM%d"), nPort);
COMFile = CreateFile(szCom.GetBuffer(50), GENERIC_READ | GENERIC_WRITE,//可¨¦讀¨¢、¡é可¨¦寫¡ä
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
if (INVALID_HANDLE_VALUE == COMFile){
return ( FALSE ) ;
}
SetupComm(COMFile,6000,6000) ;
SetCommMask(/*COMFileTemp*/COMFile, EV_RXCHAR ) ;
CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF ;
CommTimeOuts.ReadTotalTimeoutMultiplier = 0 ;
CommTimeOuts.ReadTotalTimeoutConstant = 1000 ;
CommTimeOuts.WriteTotalTimeoutMultiplier = 2*CBR_9600/9600 ;
CommTimeOuts.WriteTotalTimeoutConstant = 0 ;
SetCommTimeouts(/*COMFileTemp*/COMFile, &CommTimeOuts ) ;
dcb.DCBlength = sizeof( DCB ) ;
GetCommState(COMFile, &dcb ) ;
dcb.BaudRate =CBR_9600;
dcb.StopBits =ONESTOPBIT;
dcb.Parity = NOPARITY;
dcb.ByteSize=8;
dcb.fBinary=TRUE;
dcb.fOutxDsrFlow = 0 ;
dcb.fDtrControl = DTR_CONTROL_ENABLE ;
dcb.fOutxCtsFlow = 0 ;
dcb.fRtsControl = RTS_CONTROL_ENABLE ;
dcb.fInX = dcb.fOutX = 1 ;
dcb.XonChar = 0X11 ;
dcb.XoffChar = 0X13 ;
dcb.XonLim = 100 ;
dcb.XoffLim = 100 ;
dcb.fParity = TRUE ;
fRetVal = SetCommState(/*COMFileTemp*/COMFile, &dcb ) ;
if(!fRetVal) return FALSE;
PurgeComm( /*COMFileTemp*/COMFile, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ;
EscapeCommFunction( /*COMFileTemp*/COMFile, SETDTR ) ;
3.2發送與接收數據
HANDLE hEventArr[2];
hEventArr[0] = osRead.hEvent;
hEventArr[1] = *g_OutPutList.GetEvent();
while(1){
DWORD nResutl = WaitForMultipleObjectsEx(2, hEventArr, FALSE, 200, TRUE/*INFINITE*/);
if(0 == nResutl){
if (g_OutPutList.GetCount() > 1000)
g_OutPutList.RemoveAll();
WORD nLen = (WORD)m_nBuffLen + 2;
PBYTE pIn = new BYTE[nLen];
pIn[0] = HIBYTE(nLen);
pIn[1] = LOBYTE(nLen);
memcpy(pIn + 2, m_InPutBuff, m_nBuffLen);
g_InPutList.AddTail(pIn);
m_nBuffLen = 0;
}
else if(1 == nResutl){
PBYTE pOut = (PBYTE)g_OutPutList.RemoveHead();
int nLen = pOut[0] * 0x100 + pOut[1] - 2;
WriteCommBlock(pOut + 2, nLen);
delete[] pOut;
}
DWORD dwEvtMask = 0 ;
WaitCommEvent( COMFile, &dwEvtMask, &ShareEvent);//等̨¨待äy串ä?口¨²事º?件t
if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR) {
ReadCommBlock( );
}
}
3.3關閉串口
//禁止串行端口所有事件
SetCommMask(COMFile, 0) ;
//清除數據終端就緒信號
EscapeCommFunction( COMFile, CLRDTR ) ;
//丟棄通信資源的輸出或輸入緩沖區字符並終止在通信資源上掛起的讀、寫操操作
PurgeComm( COMFile, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ;
CloseHandle( COMFile );
COMFile = NULL