1. 程式人生 > >C/C++串列埠通訊(1)-同步操作

C/C++串列埠通訊(1)-同步操作

轉自:

串列埠通訊方式:呼叫Windows的API函式

兩種操作方式:
1. 同步操作方式
API函式會阻塞直到操作完成以後才能返回(在多執行緒方式中,雖然不會阻塞主執行緒,但是仍然會阻塞監聽執行緒);
2. 重疊操作方式(又稱為非同步操作方式
API函式會立即返回,操作在後臺進行,避免執行緒的阻塞。

通過四個步驟來完成:
(1) 開啟串列埠
(2) 配置串列埠
(3) 讀寫串列埠
(4) 關閉串列埠

1、開啟串列埠

Win32系統把檔案的概念進行了擴充套件。無論是檔案、通訊裝置、命名管道、郵件槽、磁碟、還是控制檯,都是用API函式CreateFile來開啟或建立的。
該函式的原型為:

HANDLE CreateFile( LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDistribution,
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方式開啟串列埠的示例程式碼:

HANDLE hCom; //全域性變數,串列埠控制代碼
hCom=CreateFile("COM1",//COM1口
GENERIC_READ|GENERIC_WRITE, //允許讀和寫
0, //獨佔方式
NULL,
OPEN_EXISTING, //開啟而不是建立
0, //同步方式
NULL);
if (hCom== INVALID_HANDLE_VALUE)
    {
        fprintf(stderr, "串列埠號不匹配,開啟串列埠失敗!\n");
        Sleep(3000);
        exit(0);
    }

重疊I/O開啟串列埠的示例程式碼:

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)
    {
        fprintf(stderr, "串列埠號不匹配,開啟串列埠失敗!\n");
        Sleep(3000);
        exit(0);
    }

2、配置串列埠

(1)初始化配置工作。

一般用CreateFile開啟串列埠後,可以呼叫GetCommState函式來獲取串列埠的初始配置。
要修改串列埠的配置,應該先修改DCB結構,然後再呼叫SetCommState函式設定串列埠

DCB結構包含了諸如波特率、資料位數、奇偶校驗和停止位數等資訊。 在查詢或配置串列埠的屬性時,都要用DCB結構來作為緩衝區。

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 );

(2)設定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 清除輸入緩衝區

例如:

// Clear buffer清除緩衝區 
PurgeComm(hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
PurgeComm(hCom, PURGE_TXABORT | PURGE_RXABORT);

3、讀寫串列埠

使用ReadFile和WriteFile讀寫串列埠,下面是兩個函式的宣告:

//寫--傳送命令:程式向硬體傳送指令
BOOL WriteFile( HANDLE hFile, //串列埠的控制代碼
// 寫入的資料儲存的地址, 即以該指標的值為首地址的一片記憶體區
LPCVOID lpBuffer,
//要寫入的資料的位元組數
DWORD nNumberOfBytesToWrite,
// 指向指向一個DWORD數值,該數值返回實際寫入的位元組數
LPDWORD lpNumberOfBytesWritten,
// 重疊操作時,該引數指向一個OVERLAPPED結構,
// 同步操作時,該引數為NULL。
LPOVERLAPPED lpOverlapped );
//讀--接受資料:程式讀取硬體返回的資料
BOOL ReadFile( HANDLE hFile, //串列埠的控制代碼
// 讀入的資料儲存的地址,即讀入的資料將儲存在以該指標的值為首地址的一片記憶體區
LPVOID lpBuffer,
// 要讀入的資料的位元組數
DWORD nNumberOfBytesToRead,
// 指向一個DWORD數值,該數值返回讀操作實際讀入的位元組數
LPDWORD lpNumberOfBytesRead,
// 重疊操作時,該引數指向一個OVERLAPPED結構,同步操作時,該引數為NULL。
LPOVERLAPPED lpOverlapped );

lpBuffer:char型陣列,陣列內容是定義好的指令資訊。
nNumberOfBytesToWrite,nNumberOfBytesToRead:lpBuffer陣列的元素個數

//lpBuffer:
char send_data[] = { 0x75, 0x57, 0xFF, 0x0D, 0x0A };     //請求資料 16進位制
DWORD nNumberOfBytesToWrite= 1; //讀操作實際讀入的位元組數

BYTE Rev_buffer[8]; //接受資料

在用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; 
 } 
//同步寫串列埠
 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);

4、關閉串列埠

使用CreateFile函式返回的控制代碼作為引數呼叫CloseHandle即可:

BOOL CloseHandle(
HANDLE hObject; //handle to object to close
);