1. 程式人生 > >MFC之路 第二節 串列埠通訊篇

MFC之路 第二節 串列埠通訊篇

因為我自己正好也在為專案寫一個控制軟體,所以自己做到哪就寫到哪吧。專案中軟體與下位機之間通過232串列埠進行資料通訊,所以今天打算實現串列埠通訊的相關功能,那麼就隨著我一步一步地來完成串列埠通訊功能吧。為了全面的學習串列埠通訊的各種功能,我們一起完成一個常用的串列埠通訊助手軟體。 1、第一步,先新建一個MFC的對話方塊工程。 建立完成MFC對話方塊之後,將主對話方塊上的3個預設控制元件刪除,方便我們下一步新增自己的控制元件。 2、第二步,首先完成串列埠引數設定功能和串列埠開啟關閉的功能。 在空白的對話方塊上新增一個“串列埠開啟/關閉”按鈕,一個串列埠選擇Combo控制元件,和波特率、校驗位、資料位、停止位的Combo控制元件,如下圖所示
為每一個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);
接下來開啟串列埠並設定相關引數:
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
在上面的程式註釋中已經對各條語句進行了詳細的說明,現在對其中的幾個重要語句另做一些解釋: 首先是CreateFile()函式:
m_hCom=CreateFile(m_SeriouStr,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,NULL);
這是一個多功能的函式,可用於開啟或建立以下物件,並返回可訪問的控制代碼:控制檯、通訊資源、目錄(只讀開啟)、磁碟驅動器、檔案、郵槽、管道。 函式結構如下:
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
);
返回值:如果建立成功,則返回建立的檔案的控制代碼,如果出錯則返回INVALID_HANDLE_VALUE,並會設定GetLastError值。即使函式成功,但若檔案存在,且指定了CREATE_ALWAYS 或 OPEN_ALWAYS,GetLastError也會設為ERROR_ALREADY_EXISTS。
引數說明: 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;