MFC之路 第二節 串列埠通訊篇
阿新 • • 發佈:2019-01-29
因為我自己正好也在為專案寫一個控制軟體,所以自己做到哪就寫到哪吧。專案中軟體與下位機之間通過232串列埠進行資料通訊,所以今天打算實現串列埠通訊的相關功能,那麼就隨著我一步一步地來完成串列埠通訊功能吧。為了全面的學習串列埠通訊的各種功能,我們一起完成一個常用的串列埠通訊助手軟體。
1、第一步,先新建一個MFC的對話方塊工程。
建立完成MFC對話方塊之後,將主對話方塊上的3個預設控制元件刪除,方便我們下一步新增自己的控制元件。
2、第二步,首先完成串列埠引數設定功能和串列埠開啟關閉的功能。
在空白的對話方塊上新增一個“串列埠開啟/關閉”按鈕,一個串列埠選擇Combo控制元件,和波特率、校驗位、資料位、停止位的Combo控制元件,如下圖所示
為每一個Combo控制元件新增響應的變數,如下
引數說明: lpFileName String要開啟的檔案的名或裝置名。這個字串的最大長度在ANSI版本中為MAX_PATH,在unicode版本中為32767。 dwDesiredAccess指定型別的訪問物件。如果為 GENERIC_READ 表示允許對裝置進行讀訪問;如果為 GENERIC_WRITE 表示允許對裝置進行寫訪問(可組合使用);如果為零,表示只允許獲取與一個裝置有關的資訊。
dwShareMode,共享模式, 如果是零表示不共享; 如果是FILE_SHARE_DELETE表示隨後開啟操作物件會成功只要刪除訪問請求;如果是FILE_SHARE_READ隨後開啟操作物件會成功只有請求讀訪問;如果是FILE_SHARE_WRITE 隨後開啟操作物件會成功只有請求寫訪問。 lpSecurityAttributes指向安全屬性的指標, 指向一個SECURITY_ATTRIBUTES結構的指標,定義了檔案的安全特性(如果作業系統支援的話) dwCreationDisposition,如何建立。下述常數之一: CREATE_NEW 建立檔案;如檔案存在則會出錯 CREATE_ALWAYS 建立檔案,會改寫前一個檔案 OPEN_EXISTING 檔案必須已經存在。由裝置提出要求 OPEN_ALWAYS 如檔案不存在則建立它 TRUNCATE_EXISTING 將現有檔案縮短為零長度 dwFlagsAndAttributes檔案屬性, 一個或多個下述常數 FILE_ATTRIBUTE_ARCHIVE 標記歸檔屬性 FILE_ATTRIBUTE_COMPRESSED 將檔案標記為已壓縮,或者標記為檔案在目錄中的預設壓縮方式 FILE_ATTRIBUTE_NORMAL 預設屬性 FILE_ATTRIBUTE_HIDDEN 隱藏檔案或目錄 FILE_ATTRIBUTE_READONLY 檔案為只讀 FILE_ATTRIBUTE_SYSTEM 檔案為系統檔案 FILE_FLAG_WRITE_THROUGH 作業系統不得推遲對檔案的寫操作 FILE_FLAG_OVERLAPPED 允許對檔案進行重疊操作 FILE_FLAG_NO_BUFFERING 禁止對檔案進行緩衝處理。檔案只能寫入磁碟卷的扇區塊 FILE_FLAG_RANDOM_ACCESS 針對隨機訪問對檔案緩衝進行優化 FILE_FLAG_SEQUENTIAL_SCAN 針對連續訪問對檔案緩衝進行優化 FILE_FLAG_DELETE_ON_CLOSE 關閉了上一次開啟的控制代碼後,將檔案刪除。特別適合臨時檔案 也可在Windows NT下組合使用下述常數標記: SECURITY_ANONYMOUS, SECURITY_IDENTIFICATION, SECURITY_IMPERSONATION, SECURITY_DELEGATION, SECURITY_CONTEXT_TRACKING, SECURITY_EFFECTIVE_ONLY hTemplateFile,hTemplateFile為一個檔案或裝置控制代碼,表示按這個引數給出的控制代碼為模板建立檔案(就是將該控制代碼檔案拷貝到lpFileName指定的路徑,然後再開啟)。它將指定該檔案的屬性擴充套件到新建立的檔案上面,這個引數可用於將某個新檔案的屬性設定成與現有檔案一樣,並且這樣會忽略dwAttrsAndFlags。通常這個引數設定為NULL,為空表示不使用模板,一般為空。
接下來對超時結構體進行說明:
為每一個Combo控制元件新增響應的變數,如下
接下來開啟串列埠並設定相關引數://設波特率組合列表框 TCHAR baudbuffer[][7]={"300","600","1200","2400","4800","9600","19200","38400","43000","56000","57600","115200"}; for(int i=0;i<12;i++) { int judge_tf=m_ComboBaud.AddString(baudbuffer[i]); if((judge_tf==CB_ERR)||(judge_tf==CB_ERRSPACE)) MessageBox("build baud error!"); } m_ComboBaud.SetCurSel(5); //設串列埠組合列表框 TCHAR seriou[][5]={"COM1","COM2","COM3","COM4"}; for(int i=0;i<4;i++) m_ComboSeriou.AddString(seriou[i]); m_ComboSeriou.SetCurSel(0); //設校驗位組合列表框 TCHAR jiaoyan[][7]={"N","O","E"}; for(int i=0;i<3;i++) m_ComboJiaoyan.AddString(jiaoyan[i]); m_ComboJiaoyan.SetCurSel(0); //設資料位組合列表框 TCHAR data[][2]={"8","7","6"}; for(int i=0;i<3;i++) m_ComboData.AddString(data[i]); m_ComboData.SetCurSel(0); //設停止位組合列表框 TCHAR stop[][2]={"1","2"}; for(int i=0;i<2;i++) m_ComboStop.AddString(stop[i]); m_ComboStop.SetCurSel(0);
在上面的程式註釋中已經對各條語句進行了詳細的說明,現在對其中的幾個重要語句另做一些解釋: 首先是CreateFile()函式:COMMTIMEOUTS TimeOuts; //定義超時時間 m_ComboSeriou.GetLBText(Num,m_SeriouStr); //獲取串列埠Combo下拉框中對應於Num位置的串列埠名稱,比如Num=0時,m_SeriouStr 為"COM1" m_hCom=CreateFile(m_SeriouStr,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING, //將串列埠作為一個檔案來看,用CreateFile()函式開啟串列埠,返回結果儲存在m_hCom中 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,NULL); if(m_hCom==INVALID_HANDLE_VALUE) //如果返回INVALID_HANDLE_VALUE表示開啟串列埠失敗, { AfxMessageBox(_T("開啟串列埠失敗!")); //失敗時彈出對話方塊提醒 m_bConnected=0; //將串列埠連線標誌設為0 return FALSE; //開啟失敗後不再繼續往下進行,直接返回FALSE } //設定新建立串列埠的響應事件 SetCommMask(m_hCom,EV_RXCHAR); //其中m_hCom是新建立串列埠檔案返回的控制代碼,EV_RXCHAR代表讀事件,有任何字元返回到串列埠時事件響應 SetupComm(m_hCom,MAXBLOCK,MAXBLOCK); //設定讀寫緩衝區 其中m_hCom是新建立串列埠檔案返回的控制代碼,MAXBLOCK是自己定義(#define MAXBLOCK 4096)的串列埠快取區的大小,此處為4096位元組,第2、3個引數分別為讀快取區和寫快取區大小 //設定超時 TimeOuts.ReadIntervalTimeout=MAXDWORD; //讀間隔超時 TimeOuts.ReadTotalTimeoutMultiplier=0; //讀時間係數 TimeOuts.ReadTotalTimeoutConstant=0; //讀時間常量 TimeOuts.WriteTotalTimeoutMultiplier=0; //寫時間係數 SetCommTimeouts(m_hCom,&TimeOuts); //設定串列埠引數 DCB dcb; //DCB結構,定義了串列埠通訊裝置的控制設定 if(!GetCommState(m_hCom,&dcb)) //讀取新建立的m_hCom串列埠控制代碼的DCB裝置控制塊結構體,當只需要設定一部分DCB引數時,可以通過此函式讀取現有引數,只改變部分引數即可 return FALSE; //如果讀取不成功直接結束 //設定基本引數 long baudrate[]={300,600,1200,2400,4800,9600,19200,38400,43000,56000,57600,115200}; int baudindex=m_ComboBaud.GetCurSel(); m_ComboBaud.GetLBText(baudindex,m_BaudStr); dcb.BaudRate=baudrate[baudindex]; //讀取並設定波特率引數 int databit[]={8,7,6}; int dataindex=m_ComboData.GetCurSel(); m_ComboData.GetLBText(dataindex,m_DataStr); dcb.ByteSize=databit[dataindex]; //讀取並設定資料位引數 int jiaoyanindex=m_ComboJiaoyan.GetCurSel(); m_ComboJiaoyan.GetLBText(jiaoyanindex,m_JiaoyanStr); switch(jiaoyanindex) { case 0: dcb.Parity=NOPARITY; //讀取並設定校驗位引數 break; case 1: dcb.Parity=ODDPARITY; break; case 2: dcb.Parity=EVENPARITY; break; default:; } int stopindex=m_ComboStop.GetCurSel(); m_ComboStop.GetLBText(stopindex,m_StopStr); switch(stopindex) { case 0: dcb.StopBits=ONESTOPBIT; //讀取並設定停止位引數 break; case 1: dcb.StopBits=TWOSTOPBITS; break; default:; } //流控制 dcb.fInX=TRUE; dcb.fOutX=TRUE; dcb.XonChar=XON; // #define XON 0x11 dcb.XoffChar=XOFF; // #define XOFF 0x13 dcb.XonLim=50; dcb.XoffLim=50; dcb.fNull=TRUE; BOOL SetComParameterSucceed = SetCommState(m_hCom,&dcb); //設定串列埠引數資訊,如果設定成功返回1,失敗返回0
m_hCom=CreateFile(m_SeriouStr,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,NULL);
這是一個多功能的函式,可用於開啟或建立以下物件,並返回可訪問的控制代碼:控制檯、通訊資源、目錄(只讀開啟)、磁碟驅動器、檔案、郵槽、管道。
函式結構如下:返回值:如果建立成功,則返回建立的檔案的控制代碼,如果出錯則返回INVALID_HANDLE_VALUE,並會設定GetLastError值。即使函式成功,但若檔案存在,且指定了CREATE_ALWAYS 或 OPEN_ALWAYS,GetLastError也會設為ERROR_ALREADY_EXISTS。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 );
引數說明: lpFileName String要開啟的檔案的名或裝置名。這個字串的最大長度在ANSI版本中為MAX_PATH,在unicode版本中為32767。 dwDesiredAccess指定型別的訪問物件。如果為 GENERIC_READ 表示允許對裝置進行讀訪問;如果為 GENERIC_WRITE 表示允許對裝置進行寫訪問(可組合使用);如果為零,表示只允許獲取與一個裝置有關的資訊。
dwShareMode,共享模式, 如果是零表示不共享; 如果是FILE_SHARE_DELETE表示隨後開啟操作物件會成功只要刪除訪問請求;如果是FILE_SHARE_READ隨後開啟操作物件會成功只有請求讀訪問;如果是FILE_SHARE_WRITE 隨後開啟操作物件會成功只有請求寫訪問。 lpSecurityAttributes指向安全屬性的指標, 指向一個SECURITY_ATTRIBUTES結構的指標,定義了檔案的安全特性(如果作業系統支援的話) dwCreationDisposition,如何建立。下述常數之一: CREATE_NEW 建立檔案;如檔案存在則會出錯 CREATE_ALWAYS 建立檔案,會改寫前一個檔案 OPEN_EXISTING 檔案必須已經存在。由裝置提出要求 OPEN_ALWAYS 如檔案不存在則建立它 TRUNCATE_EXISTING 將現有檔案縮短為零長度 dwFlagsAndAttributes檔案屬性, 一個或多個下述常數 FILE_ATTRIBUTE_ARCHIVE 標記歸檔屬性 FILE_ATTRIBUTE_COMPRESSED 將檔案標記為已壓縮,或者標記為檔案在目錄中的預設壓縮方式 FILE_ATTRIBUTE_NORMAL 預設屬性 FILE_ATTRIBUTE_HIDDEN 隱藏檔案或目錄 FILE_ATTRIBUTE_READONLY 檔案為只讀 FILE_ATTRIBUTE_SYSTEM 檔案為系統檔案 FILE_FLAG_WRITE_THROUGH 作業系統不得推遲對檔案的寫操作 FILE_FLAG_OVERLAPPED 允許對檔案進行重疊操作 FILE_FLAG_NO_BUFFERING 禁止對檔案進行緩衝處理。檔案只能寫入磁碟卷的扇區塊 FILE_FLAG_RANDOM_ACCESS 針對隨機訪問對檔案緩衝進行優化 FILE_FLAG_SEQUENTIAL_SCAN 針對連續訪問對檔案緩衝進行優化 FILE_FLAG_DELETE_ON_CLOSE 關閉了上一次開啟的控制代碼後,將檔案刪除。特別適合臨時檔案 也可在Windows NT下組合使用下述常數標記: SECURITY_ANONYMOUS, SECURITY_IDENTIFICATION, SECURITY_IMPERSONATION, SECURITY_DELEGATION, SECURITY_CONTEXT_TRACKING, SECURITY_EFFECTIVE_ONLY hTemplateFile,hTemplateFile為一個檔案或裝置控制代碼,表示按這個引數給出的控制代碼為模板建立檔案(就是將該控制代碼檔案拷貝到lpFileName指定的路徑,然後再開啟)。它將指定該檔案的屬性擴充套件到新建立的檔案上面,這個引數可用於將某個新檔案的屬性設定成與現有檔案一樣,並且這樣會忽略dwAttrsAndFlags。通常這個引數設定為NULL,為空表示不使用模板,一般為空。
接下來對超時結構體進行說明:
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout; // 讀間隔超時
DWORD ReadTotalTimeoutMultiplier; // 讀時間係數
DWORD ReadTotalTimeoutConstant; // 讀時間常量
DWORD WriteTotalTimeoutMultiplier; // 寫時間係數
DWORD WriteTotalTimeoutConstant; // 寫時間常量
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
在用ReadFile和WriteFile讀寫序列口時,需要考慮超時問題。如果在指定的時間內沒有讀出或寫入指定數量的字元,那麼ReadFile或WriteFile的操作就會結束。要查詢當前的超時設定應呼叫GetCommTimeouts函式,該函式會填充一個COMMTIMEOUTS結構。呼叫SetCommTimeouts可以用某一個COMMTIMEOUTS結構的內容來設定超時。
有兩種超時:間隔超時和總超時。間隔超時是指在接收時兩個字元之間的最大時延,總超時是指讀寫操作總共花費的最大時間。寫操作只支援總超時,而讀操作兩種超時均支援。
COMMTIMEOUTS結構的成員都以毫秒為單位。
ReadIntervalTimeout:兩字元之間最大的延時,當讀取串列埠資料時,一旦兩個字元傳輸的時間差超過該時間,讀取函式將返回現有的資料。設定為0表示該引數不起作用。指定時間最大值(毫秒),允許接收的2個位元組間有時間差。也就 是說,剛接收了一個位元組後,等了ReadIntervalTimeout時間後還沒有新的位元組到達,就 認為本次讀串列埠操作結束(後面的位元組等下一次讀取操作來處理)。即使你想讀8個位元組,但讀第2個位元組後,過了ReadIntervalTimeout時間後,第3個位元組還沒到。實際上就只讀了2個位元組。
ReadTotalTimeoutMultiplier:指定比例因子(毫秒),實際上是設定讀取一個位元組和等待下一個位元組所需的時間,這樣總的超時時間為讀取的位元組數乘以該值,同樣一次讀取操作到達這個時間後,也認為本次讀操作己經結束。
ReadTotalTimeoutConstant:一次讀取串列埠資料的固定超時。所以在一次讀取串列埠的操作中,其超時為ReadTotalTimeoutMultiplier乘以讀取的位元組數再加上 ReadTotalTimeoutConstant。將ReadIntervalTimeout設定為MAXDWORD,並將ReadTotalTimeoutMultiplier 和ReadTotalTimeoutConstant設定為0,表示讀取操作將立即返回存放在輸入緩衝區的字元。可以理解為一個修正時間,實際上就是按ReadTotalTimeoutMultiplier計算出的超時時間再加上該時間才作為整個超時時間。
WriteTotalTimeoutMultiplier:寫入每字元間的超時。
WriteTotalTimeoutConstant:一次寫入串列埠資料的固定超時。所以在一次寫入串列埠的操作中,其超時為WriteTotalTimeoutMultiplier乘以寫入的位元組數再加上 WriteTotalTimeoutConstant。
總超時的計算公式是:
總超時=時間係數×要求讀/寫的字元數 + 時間常量
例如,如果要讀入10個字元,那麼讀操作的總超時的計算公式為:
讀總超時=ReadTotalTimeoutMultiplier×10 + ReadTotalTimeoutConstant
在用重疊方式讀寫序列口時,雖然ReadFile和WriteFile在完成操作以前就可能返回,但超時仍然是起作用的。在這種情況下,超時規定的是操作的完成時間,而不是ReadFile和WriteFile的返回時間。
還有一個需要說明的是DCB結構體,DCB(Device Control Block)結構定義了串列埠通訊裝置的控制設定。
typedef struct _DCB {
DWORD DCBlength; //DCB結構大小,即sizeof(DCB),在呼叫SetCommState來更新DCB前必須作設定
DWORD BaudRate; //指定當前採用的波特率,應與所連線的通訊裝置相匹配
DWORD fBinary: 1; //指定是否允許二進位制模式。Win32 API不支援非二進位制模式傳輸,應設定為true
DWORD fParity: 1; //指定奇偶校驗是否允許,在為true時具體採用何種校驗看Parity 設定
DWORD fOutxCtsFlow:1; //是否監控CTS(clear-to-send)訊號來做輸出流控。
//當設定為true時: 若CTS為低電平,則資料傳送將被掛起,直至CTS變為高。
//CTS的訊號一般由DCE(通常是一個Modem)控制,DTE(通常是計算機)傳送資料時監測CTS訊號。
//也就是說DCE通過把CTS置高來表明自己可以接收資料了
DWORD fDtrControl:2;
DWORD fDsrSensitivity:1; // 通訊裝置是否對DSR訊號敏感。若設定為TRUE,則當DSR為低時將會忽略所有接收的位元組
DWORD fTXContinueOnXoff:1; //當輸入緩衝區滿且驅動程式已發出XOFF字元時,是否停止傳送。 當為TRUE時,XOFF被髮送後傳送仍然會繼續;為FALSE時,則 //傳送會停止, 直至輸入緩衝區有XonLim位元組的空餘空間、驅動程式已傳送XON字元之後傳送繼續
DWORD fOutX: 1; //XON/XOFF 流量控制在傳送時是否可用。 如果為TRUE, 當 XOFF 值被收到的時候,傳送停止;當 XON 值被收到的時候,發 //送繼續
DWORD fInX: 1; //XON/XOFF 流量控制在接收時是否可用。 如果為TRUE, 當 輸入緩衝區已接收滿XoffLim 位元組時,傳送XOFF字元; 當輸入緩 //衝區已經有XonLim 位元組的空餘容量時,傳送XON字元
DWORD fErrorChar: 1; //該值為TRUE,則用ErrorChar指定的字元代替奇偶校驗錯誤的接收字元
DWORD fNull: 1; //為TRUE時,接收時自動去掉空(0值)位元組
DWORD fRtsControl:2; //設定RTS (request-to-send)流控,若為0則預設取 RTS_CONTROL_HANDSHAKE。可取值及其意義:
// 取值 意義
// RTS_CONTROL_DISABLE 開啟裝置時置RTS訊號為低電平,應用程式可通過呼叫 EscapeCommFunction函式/ //來改變RTS線電平狀態
// RTS_CONTROL_ENABLE 開啟裝置時置RTS訊號為高電平,應用程式可通過呼叫 EscapeCommFunction函式/ //來改變RTS線電平狀態
// RTS_CONTROL_HANDSHAKE 允許RTS訊號握手,此時應用程式不能呼叫EscapeCommFunction函式。 當輸入緩/ //衝區已經有足夠空間接收資料時,驅動程式置RTS為高以允許 DCE來發送;反之置R // TS為低以阻止DCE傳送資料。
// RTS_CONTROL_TOGGLE 有位元組要傳送時RTS變高,當所有緩衝位元組已被髮送完畢後,RTS變低。
DWORD fAbortOnError:1; //讀寫操作發生錯誤時是否取消操作。若設定為true,則當發生讀寫錯誤時,將取消所有讀寫操作 (錯誤狀態置為ERROR_IO_A //BORTED),直到呼叫ClearCommError函式後才能重新進行通訊操作
DWORD fDummy2:17; //保留,未啟用
WORD wReserved; //未啟用,必須設定為0
WORD XonLim; //在XON字元傳送前接收緩衝區內可允許的最小位元組數
WORD XoffLim; //在XOFF字元傳送前接收緩衝區內可允許的最大位元組數
BYTE ByteSize;
BYTE Parity; // 指定埠資料傳輸的校驗方法。以下是可取值及其意義:
// 取值 意義
//EVENPARITY 偶校驗
//MARKPARITY 標記校驗,所發信息幀第9位恆為1
//NOPARITY 無校驗
//ODDPARITY 奇校驗
DWORD fOutxDsrFlow:1; //是否監控DSR (data-set-ready) 訊號來做輸出流控。當設定為true時:若DSR為低電平,則資料傳送將被掛起,直至DSR變 //為高。DSR的訊號一般由DCE來控制 fDtrControl DTR (data-terminal-ready)流控,可取值如下:
// 取值 意義
// DTR_CONTROL_DISABLE 開啟裝置時置DTR訊號為低電平,應用程式可通過呼叫
// EscapeCommFunction 函式來改變DTR線電平狀態
// DTR_CONTROL_ENABLE 開啟裝置時置DTR訊號為高電平,應用程式可通過呼叫
// EscapeCommFunction 函式來改變DTR線電平狀態
// DTR_CONTROL_HANDSHAKE 允許DTR訊號握手,此時應用程式不能呼叫EscapeCommFunction函式
BYTE StopBits; //指定埠當前使用的停止位數,可取值:
//取值 意義
//ONESTOPBIT 1停止位
//ONE5STOPBITS 1.5停止位
//TWOSTOPBITS 2停止位
char XonChar; //指定XON字元
char XoffChar; //指定XOFF字元
char ErrorChar; //指定ErrorChar字元(代替接收到的奇偶校驗發生錯誤時的位元組)
char EofChar; //指定用於標示資料結束的字元
char EvtChar; // 當接收到此字元時,會產生一個EV_RXFLAG事件,如果用SetCommMask函式中指定了EV_RXFLAG ,則可用WaitCommEvent 來監 //測該事件
WORD wReserved1; //保留,未啟用
} DCB;