串口通信簡介
串行接口是一種可以將接受來自CPU的并行數據字符轉換為連續的串行數據流發送出去,同時可將接受的串行數據流轉換為并行的數據字符供給CPU的器件。一般完成這種功能的電路,我們稱為串行接口電路。
串口通信結構
串口通信是指外設和計算機間,通過數據信號線 、地線、控制線等,按位進行傳輸數據的一種通訊方式。這種通信方式使用的數據線少,在遠距離通信中可以節約通信成本,但其傳輸速度比并行傳輸低。
串口是計算機上一種非常通用的設備通信協議。大多數計算機(不包括筆記本電腦)包含兩個基于RS-232的串口。串口同時也是儀器儀表設備通用的通信協議;很多GPIB兼容的設備也帶有RS-232口。同時,串口通信協議也可以用于獲取遠程采集設備的數據。
RS-232(ANSI/EIA-232標準)是IBM-PC及其兼容機上的串行連接標準。可用于許多用途,比如連接鼠標、打印機或者Modem,同時也可以接工業儀器儀表。用于驅動和連線的改進,實際應用中RS-232的傳輸長度或者速度常常超過標準的值。RS-232只限于PC串口和設備間點對點的通信。RS-232串口通信最遠距離是50英尺。
串口通信程序圖框
通信編程的內容
1. 通信手段(“用嘴講話”/“眉目傳情” 等等)。例如:串口通信,TCP, UDP通信等等。
2. 通信協議(“普通話”,“心有靈犀一點通”等等)。例如:Modem 指令集,telnet 協議,SMTP/POP3協議,“心有靈犀”協議等等,就如“普通話”的通用性,國際制定的標準也就是通用的協議,而“心有靈犀”就是特定的協議,只能用于兩個人之間,當然,不排除多人的“心有靈犀一點通”,但不可能和大的范圍J.
vc串口通信編程詳解
在工業控制中,工控機(一般都基于Windows平臺)經常需要與智能儀表通過串口進行通信。串口通信方便易行,應用廣泛。
一般情況下,工控機和各智能儀表通過RS485總線進行通信。RS485的通信方式是半雙工的,只能由作為主節點的工控PC機依次輪詢網絡上的各智能控制單元子節點。每次通信都是由PC機通過串口向智能控制單元發布命令,智能控制單元在接收到正確的命令后作出應答。
在Win32下,可以使用兩種編程方式實現串口通信,其一是使用ActiveX控件,這種方法程序簡單,但欠靈活。其二是調用Windows的API函數,這種方法可以清楚地掌握串口通信的機制,并且自由靈活。本文我們只介紹API串口通信部分。
串口的操作可以有兩種操作方式:同步操作方式和重疊操作方式(又稱為異步操作方式)。同步操作時,API函數會阻塞直到操作完成以后才能返回(在多線程方式中,雖然不會阻塞主線程,但是仍然會阻塞監聽線程);而重疊操作方式,API函數會立即返回,操作在后臺進行,避免線程的阻塞。
無論那種操作方式,一般都通過四個步驟來完成:
(1) 打開串口
(2) 配置串口
(3) 讀寫串口
(4) 關閉串口
1、打開串口
Win32系統把文件的概念進行了擴展。無論是文件、通信設備、命名管道、郵件槽、磁盤、還是控制臺,都是用API函數CreateFile來打開或創建的。該函數的原型為:
C++代碼
HANDLE CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDistribution, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
lpFileName:將要打開的串口邏輯名,如“COM1”;
dwDesiredAccess:指定串口訪問的類型,可以是讀取、寫入或二者并列;
dwShareMode:指定共享屬性,由于串口不能共享,該參數必須置為0;
lpSecurityAttributes:引用安全性屬性結構,缺省值為NULL;
dwCreationDistribution:創建標志,對串口操作該參數必須置為OPEN_EXISTING;
dwFlagsAndAttributes:屬性描述,用于指定該串口是否進行異步操作,該值為FILE_FLAG_OVERLAPPED,表示使用異步的I/O;該值為0,表示同步I/O操作;
hTemplateFile:對串口而言該參數必須置為NULL。
同步I/O方式打開串口的示例代碼:
C++代碼
HANDLE hCom; //全局變量,串口句柄
hCom=CreateFile(“COM1”,//COM1口
GENERIC_READ|GENERIC_WRITE, //允許讀和寫
0, //獨占方式
NULL,
OPEN_EXISTING, //打開而不是創建
0, //同步方式
NULL);
if(hCom==(HANDLE)-1)
{
AfxMessageBox(“打開COM失敗!”);
return FALSE;
}
return TRUE;
重疊I/O打開串口的示例代碼:
C++代碼
HANDLE hCom; //全局變量,串口句柄
hCom =CreateFile(“COM1”, //COM1口
GENERIC_READ|GENERIC_WRITE, //允許讀和寫
0, //獨占方式
NULL,
OPEN_EXISTING, //打開而不是創建
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //重疊方式
NULL);
if(hCom ==INVALID_HANDLE_VALUE)
{
AfxMessageBox(“打開COM失敗!”);
return FALSE;
}
return TRUE;
2、配置串口
在打開通訊設備句柄后,常常需要對串口進行一些初始化配置工作。這需要通過一個DCB結構來進行。DCB結構包含了諸如波特率、數據位數、奇偶校驗和停止位數等信息。在查詢或配置串口的屬性時,都要用DCB結構來作為緩沖區。
一般用CreateFile打開串口后,可以調用GetCommState函數來獲取串口的初始配置。要修改串口的配置,應該先修改DCB結構,然后再調用SetCommState函數設置串口。
DCB結構包含了串口的各項參數設置,下面僅介紹幾個該結構常用的變量:
typedef struct _DCB{ ………
DWORD BaudRate;//波特率,指定通信設備的傳輸速率。這個成員可以是實際波特率值或者下面的常量值之一: CBR_110,CBR_300,CBR_600,CBR_1200,CBR_2400,CBR_4800,CBR_9600,CBR_19200, CBR_38400, CBR_56000, CBR_57600, CBR_115200, CBR_128000, CBR_256000, CBR_14400
DWORD fParity; // 指定奇偶校驗使能。若此成員為1,允許奇偶校驗檢查 …
BYTE ByteSize; // 通信字節位數,4—8
BYTE Parity; //指定奇偶校驗方法。此成員可以有下列值: EVENPARITY 偶校驗 NOPARITY 無校驗 MARKPARITY 標記校驗 ODDPARITY 奇校驗
BYTE StopBits; //指定停止位的位數。此成員可以有下列值: ONESTOPBIT 1位停止位 TWOSTOPBITS 2位停止位
ON 5STOPBITS 1.5位停止位
GetCommState函數可以獲得COM口的設備控制塊,從而獲得相關參數:
BOOL GetCommState(
HANDLE hFile, //標識通訊端口的句柄
LPDCB lpDCB //指向一個設備控制塊(DCB結構)的指針 );
SetCommState函數設置COM口的設備控制塊:
BOOL SetCommState( HANDLE hFile, LPDCB lpDCB );
除了在BCD中的設置外,程序一般還需要設置I/O緩沖區的大小和超時。Windows用I/O緩沖區來暫存串口輸入和輸出的數據。如果通信的速率較高,則應該設置較大的緩沖區。調用SetupComm函數可以設置串行口的輸入和輸出緩沖區的大小。
BOOL SetupComm( HANDLE hFile, // 通信設備的句柄
DWORD dwInQueue, // 輸入緩沖區的大小(字節數)
DWORD dwOutQueue // 輸出緩沖區的大小(字節數) );
在用ReadFile和WriteFile讀寫串行口時,需要考慮超時問題。超時的作用是在指定的時間內沒有讀入或發送指定數量的字符,ReadFile或WriteFile的操作仍然會結束。
要查詢當前的超時設置應調用GetCommTimeouts函數,該函數會填充一個COMMTIMEOUTS結構。調用SetCommTimeouts可以用某一個COMMTIMEOUTS結構的內容來設置超時。
讀寫串口的超時有兩種:間隔超時和總超時。間隔超時是指在接收時兩個字符之間的最大時延。總超時是指讀寫操作總共花費的最大時間。寫操作只支持總超時,而讀操作兩種超時均支持。用COMMTIMEOUTS結構可以規定讀寫操作的超時。
COMMTIMEOUTS結構的定義為:
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout; //讀間隔超時
DWORD ReadTotalTimeoutMultiplier; //讀時間系數
DWORD ReadTotalTimeoutConstant; //讀時間常量
DWORD WriteTotalTimeoutMultiplier; // 寫時間系數
DWORD WriteTotalTimeoutConstant; //寫時間常量
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
COMMTIMEOUTS結構的成員都以毫秒為單位。
總超時的計算公式是:總超時=時間系數×要求讀/寫的字符數+時間常量
例如,要讀入10個字符,那么讀操作的總超時的計算公式為:
讀總超時=ReadTotalTimeoutMultiplier×10+ReadTotalTimeoutConstant
可以看出:間隔超時和總超時的設置是不相關的,這可以方便通信程序靈活地設置各種超時。
如果所有寫超時參數均為0,那么就不使用寫超時。如果ReadIntervalTimeout為0,那么就不使用讀間隔超時。如果ReadTotalTimeoutMultiplier 和 ReadTotalTimeoutConstant 都為0,則不使用讀總超時。如果讀間隔超時被設置成MAXDWORD并且讀時間系數和讀時間常量都為0,那么在讀一次輸入緩沖區的內容后讀操作就立即返回,而不管是否讀入了要求的字符。
在用重疊方式讀寫串口時,雖然ReadFile和WriteFile在完成操作以前就可能返回,但超時仍然是起作用的。在這種情況下,超時規定的是操作的完成時間,而不是ReadFile和WriteFile的返回時間。
配置串口的示例代碼:
SetupComm(hCom,1024,1024); //輸入緩沖區和輸出緩沖區的大小都是1024
COMMTIMEOUTS TimeOuts; //設定讀超時
TimeOuts.ReadIntervalTimeout=1000;
TimeOuts.ReadTotalTimeoutMultiplier=500;
TimeOuts.ReadTotalTimeoutConstant=5000; //設定寫超時
TimeOuts.WriteTotalTimeoutMultiplier=500;
TimeOuts.WriteTotalTimeoutConstant=2000;
SetCommTimeouts(hCom,&TimeOuts); //設置超時
DCB dcb;
GetCommState(hCom,&dcb);
dcb.BaudRate=9600; //波特率為9600
dcb.ByteSize=8; //每個字節有8位
dcb.Parity=NOPARITY; //無奇偶校驗位
dcb.StopBits=TWOSTOPBITS; //兩個停止位
SetCommState(hCom,&dcb);
PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);
在讀寫串口之前,還要用PurgeComm()函數清空緩沖區,該函數原型:
BOOL PurgeComm( HANDLE hFile, //串口句柄
DWORD dwFlags // 需要完成的操作 );
參數dwFlags指定要完成的操作,可以是下列值的組合:
PURGE_TXABORT 中斷所有寫操作并立即返回,即使寫操作還沒有完成。
PURGE_RXABORT 中斷所有讀操作并立即返回,即使讀操作還沒有完成。
PURGE_TXCLEAR 清除輸出緩沖區
PURGE_RXCLEAR 清除輸入緩沖區
3、讀寫串口
我們使用ReadFile和WriteFile讀寫串口,下面是兩個函數的聲明:
BOOL ReadFile( HANDLE hFile, //串口的句柄
// 讀入的數據存儲的地址,
// 即讀入的數據將存儲在以該指針的值為首地址的一片內存區
LPVOID lpBuffer,
// 要讀入的數據的字節數
DWORD nNumberOfBytesToRead,
// 指向一個DWORD數值,該數值返回讀操作實際讀入的字節數
LPDWORD lpNumberOfBytesRead,
// 重疊操作時,該參數指向一個OVERLAPPED結構,同步操作時,該參數為NULL。
LPOVERLAPPED lpOverlapped );
BOOL WriteFile( HANDLE hFile, //串口的句柄
// 寫入的數據存儲的地址,
// 即以該指針的值為首地址的
LPCVOID lpBuffer,
//要寫入的數據的字節數
DWORD nNumberOfBytesToWrite,
// 指向指向一個DWORD數值,該數值返回實際寫入的字節數
LPDWORD lpNumberOfBytesWritten,
// 重疊操作時,該參數指向一個OVERLAPPED結構,
// 同步操作時,該參數為NULL。
LPOVERLAPPED lpOverlapped );
在用ReadFile和WriteFile讀寫串口時,既可以同步執行,也可以重疊執行。在同步執行時,函數直到操作完成后才返回。這意味著同步執行時線程會被阻塞,從而導致效率下降。在重疊執行時,即使操作還未完成,這兩個函數也會立即返回,費時的I/O操作在后臺進行。
ReadFile和WriteFile函數是同步還是異步由CreateFile函數決定,如果在調用CreateFile創建句柄時指定了FILE_FLAG_OVERLAPPED標志,那么調用ReadFile和WriteFile對該句柄進行的操作就應該是重疊的;如果未指定重疊標志,則讀寫操作應該是同步的。ReadFile和WriteFile函數的同步或者異步應該和CreateFile函數相一致。
ReadFile函數只要在串口輸入緩沖區中讀入指定數量的字符,就算完成操作。而WriteFile函數不但要把指定數量的字符拷入到輸出緩沖區,而且要等這些字符從串行口送出去后才算完成操作。
如果操作成功,這兩個函數都返回TRUE。需要注意的是,當ReadFile和WriteFile返回FALSE時,不一定就是操作失敗,線程應該調用GetLastError函數分析返回的結果。例如,在重疊操作時如果操作還未完成函數就返回,那么函數就返回FALSE,而且GetLastError函數返回ERROR_IO_PENDING。這說明重疊操作還未完成。
同步方式讀寫串口比較簡單,下面先例舉同步方式讀寫串口的代碼:
//同步讀串口
char str[100];
DWORD wCount;//讀取的字節數
BOOL bReadStat;
bReadStat=ReadFile(hCom,str,100,&wCount,NULL);
if(!bReadStat) { AfxMessageBox(“讀串口失敗!”); return FALSE; } return TRUE; //同步寫串口
char lpOutBuffer[100];
DWORD dwBytesWrite=100;
COMSTAT ComStat;
DWORD dwErrorFlags;
BOOL bWriteStat;
ClearCommError(hCom,&dwErrorFlags,&ComStat);
bWriteStat=WriteFile(hCom,lpOutBuffer,dwBytesWrite,& dwBytesWrite,NULL);
if(!bWriteStat) { AfxMessageBox(“寫串口失敗!”); }
PurgeComm(hCom, PURGE_TXABORT| PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
在重疊操作時,操作還未完成函數就返回。
重疊I/O非常靈活,它也可以實現阻塞(例如我們可以設置一定要讀取到一個數據才能進行到下一步操作)。有兩種方法可以等待操作完成:一種方法是用象WaitForSingleObject這樣的等待函數來等待OVERLAPPED結構的hEvent成員;另一種方法是調用GetOverlappedResult函數等待,后面將演示說明。
下面我們先簡單說一下OVERLAPPED結構和GetOverlappedResult函數:
OVERLAPPED結構
OVERLAPPED結構包含了重疊I/O的一些信息,定義如下:
typedef struct _OVERLAPPED { // o
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
在使用ReadFile和WriteFile重疊操作時,線程需要創建OVERLAPPED結構以供這兩個函數使用。線程通過OVERLAPPED結構獲得當前的操作狀態,該結構最重要的成員是hEvent。hEvent是讀寫事件。當串口使用異步通訊時,函數返回時操作可能還沒有完成,程序可以通過檢查該事件得知是否讀寫完畢。
當調用ReadFile, WriteFile 函數的時候,該成員會自動被置為無信號狀態;當重疊操作完成后,該成員變量會自動被置為有信號狀態。
GetOverlappedResult函數 BOOL GetOverlappedResult( HANDLE hFile, // 串口的句柄 // 指向重疊操作開始時指定的OVERLAPPED結構 LPOVERLAPPED lpOverlapped, // 指向一個32位變量,該變量的值返回實際讀寫操作傳輸的字節數。 LPDWORD lpNumberOfBytesTransferred, // 該參數用于指定函數是否一直等到重疊操作結束。 // 如果該參數為TRUE,函數直到操作結束才返回。 // 如果該參數為FALSE,函數直接返回,這時如果操作沒有完成, // 通過調用GetLastError()函數會返回ERROR_IO_INCOMPLETE。 BOOL bWait );
該函數返回重疊操作的結果,用來判斷異步操作是否完成,它是通過判斷OVERLAPPED結構中的hEvent是否被置位來實現的。
異步讀串口的示例代碼:
char lpInBuffer[1024];
DWORD dwBytesRead=1024;
COMSTAT ComStat;
DWORD dwErrorFlags;
OVERLAPPED m_osRead;
memset(&m_osRead,0,sizeof(OVERLAPPED));
m_osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
ClearCommError(hCom,&dwErrorFlags,&ComStat);
dwBytesRead=min(dwBytesRead,(DWORD)ComStat.cbInQue);
if(!dwBytesRead) return FALSE;
BOOL bReadStatus;
bReadStatus=ReadFile(hCom,lpInBuffer, dwBytesRead,&dwBytesRead,&m_osRead);
if(!bReadStatus)
//如果ReadFile函數返回FALSE
{
if(GetLastError()==ERROR_IO_PENDING)
//GetLastError()函數返回ERROR_IO_PENDING,表明串口正在進行讀操作
{
WaitForSingleObject(m_osRead.hEvent,2000);
//使用WaitForSingleObject函數等待,直到讀操作完成或延時已達到2秒鐘
//當串口讀操作進行完畢后,m_osRead的hEvent事件會變為有信號
PurgeComm(hCom, PURGE_TXABORT| PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
return dwBytesRead;
}
return 0;
}
PurgeComm(hCom, PURGE_TXABORT| PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
return dwBytesRead;
對以上代碼再作簡要說明:
在使用ReadFile 函數進行讀操作前,應先使用ClearCommError函數清除錯誤。
ClearCommError函數的原型如下:
BOOL ClearCommError( HANDLE hFile, // 串口句柄
LPDWORD lpErrors, // 指向接收錯誤碼的變量
LPCOMSTAT lpStat // 指向通訊狀態緩沖區 );
該函數獲得通信錯誤并報告串口的當前狀態,同時,該函數清除串口的錯誤標志以便繼續輸入、輸出操作。
參數lpStat指向一個COMSTAT結構,該結構返回串口狀態信息。
COMSTAT結構 COMSTAT結構包含串口的信息,結構定義如下:
typedef struct _COMSTAT { // cst DWORD fCtsHold : 1; // Tx waiting for CTS signal DWORD fDsrHold : 1; // Tx waiting for DSR signal DWORD fRlsdHold : 1; // Tx waiting for RLSD signal DWORD fXoffHold : 1; // Tx waiting, XOFF char rec‘’d DWORD fXoffSent : 1; // Tx waiting, XOFF char sent DWORD fEof : 1; // EOF character sent DWORD fTxim : 1; // character waiting for Tx DWORD fReserved : 25; // reserved DWORD cbInQue; // bytes in input buffer DWORD cbOutQue; // bytes in output buffer } COMSTAT, *LPCOMSTAT;
本文只用到了cbInQue成員變量,該成員變量的值代表輸入緩沖區的字節數。
最后用PurgeComm函數清空串口的輸入輸出緩沖區。
這段代碼用WaitForSingleObject函數來等待OVERLAPPED結構的hEvent成員,下面我們再演示一段調用GetOverlappedResult函數等待的異步讀串口示例代碼:
char lpInBuffer[1024];
DWORD dwBytesRead=1024;
BOOL bReadStatus;
DWORD dwErrorFlags;
COMSTAT ComStat;
OVERLAPPED m_osRead;
ClearCommError(hCom,&dwErrorFlags,&ComStat);
if(!ComStat.cbInQue) return 0;
dwBytesRead=min(dwBytesRead,(DWORD)ComStat.cbInQue);
4、關閉串口
利用API函數關閉串口非常簡單,只需使用CreateFile函數返回的句柄作為參數調用CloseHandle即可:
BOOL CloseHandle(
HANDLE hObject; //handle to object to close
);
bReadStatus=ReadFile(hCom, lpInBuffer,dwBytesRead, &dwBytesRead,&m_osRead);
if(!bReadStatus) //如果ReadFile函數返回FALSE
{ if(GetLastError()==ERROR_IO_PENDING)
{ GetOverlappedResult(hCom, &m_osRead,&dwBytesRead,TRUE);
// GetOverlappedResult函數的最后一個參數設為TRUE,
//函數會一直等待,直到讀操作完成或由于錯誤而返回。
return dwBytesRead; }
return 0; }
return dwBytesRead;
異步寫串口的示例代碼:
char buffer[1024];
DWORD dwBytesWritten=1024;
DWORD dwErrorFlags;
COMSTAT ComStat;
OVERLAPPED m_osWrite;
BOOL bWriteStat;
bWriteStat=WriteFile(hCom,buffer,dwBytesWritten, &dwBytesWritten,&m_OsWrite);
if(!bWriteStat)
{ if(GetLastError()==ERROR_IO_PENDING)
{ WaitForSingleObject(m_osWrite.hEvent,1000);
return dwBytesWritten; }
return 0; }
return dwBytesWritten;
評論
查看更多