在工業(yè)控制中,工控機(jī)(一般都基于Windows平臺(tái))經(jīng)常需要與智能儀表通過串口進(jìn)行通信。串口通信方便易行,應(yīng)用廣泛。 無論那種操作方式,一般都通過四個(gè)步驟來完成: (1) 打開串口 Win32系統(tǒng)把文件的概念進(jìn)行了擴(kuò)展。無論是文件、通信設(shè)備、命名管道、郵件槽、磁盤、還是控制臺(tái),都是用API函數(shù)CreateFile來打開或創(chuàng)建的。該函數(shù)的原型為: HANDLE CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDistribution, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
同步I/O方式打開串口的示例代碼: HANDLE hCom; //全局變量,串口句柄 hCom=CreateFile("COM1",//COM1口 GENERIC_READ|GENERIC_WRITE, //允許讀和寫 0, //獨(dú)占方式 NULL, OPEN_EXISTING, //打開而不是創(chuàng)建 0, //同步方式 NULL); if(hCom==(HANDLE)-1) { AfxMessageBox("打開COM失敗!"); return FALSE; } return TRUE;重疊I/O打開串口的示例代碼: HANDLE hCom; //全局變量,串口句柄 hCom =CreateFile("COM1", //COM1口 GENERIC_READ|GENERIC_WRITE, //允許讀和寫 0, //獨(dú)占方式 NULL, OPEN_EXISTING, //打開而不是創(chuàng)建 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //重疊方式 NULL); if(hCom ==INVALID_HANDLE_VALUE) { AfxMessageBox("打開COM失敗!"); return FALSE; } return TRUE;(2)、配置串口 在打開通訊設(shè)備句柄后,常常需要對串口進(jìn)行一些初始化配置工作。這需要通過一個(gè)DCB結(jié)構(gòu)來進(jìn)行。DCB結(jié)構(gòu)包含了諸如波特率、數(shù)據(jù)位數(shù)、奇偶校驗(yàn)和停止位數(shù)等信息。在查詢或配置串口的屬性時(shí),都要用DCB結(jié)構(gòu)來作為緩沖區(qū)。 typedef struct _DCB{ ……… //波特率,指定通信設(shè)備的傳輸速率。這個(gè)成員可以是實(shí)際波特率值或者下面的常量值之一: 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; // 指定奇偶校驗(yàn)使能。若此成員為1,允許奇偶校驗(yàn)檢查 … BYTE ByteSize; // 通信字節(jié)位數(shù),4—8 BYTE Parity; //指定奇偶校驗(yàn)方法。此成員可以有下列值: EVENPARITY 偶校驗(yàn) NOPARITY 無校驗(yàn) MARKPARITY 標(biāo)記校驗(yàn) ODDPARITY 奇校驗(yàn) BYTE StopBits; //指定停止位的位數(shù)。此成員可以有下列值: ONESTOPBIT 1位停止位 TWOSTOPBITS 2位停止位 ONE5STOPBITS 1.5位停止位 ……… } DCB; winbase.h文件中定義了以上用到的常量。如下: #define NOPARITY 0 #define ODDPARITY 1 #define EVENPARITY 2 #define ONESTOPBIT 0 #define ONE5STOPBITS 1 #define TWOSTOPBITS 2 #define CBR_110 110 #define CBR_300 300 #define CBR_600 600 #define CBR_1200 1200 #define CBR_2400 2400 #define CBR_4800 4800 #define CBR_9600 9600 #define CBR_14400 14400 #define CBR_19200 19200 #define CBR_38400 38400 #define CBR_56000 56000 #define CBR_57600 57600 #define CBR_115200 115200 #define CBR_128000 128000 #define CBR_256000 256000GetCommState函數(shù)可以獲得COM口的設(shè)備控制塊,從而獲得相關(guān)參數(shù): BOOL GetCommState( HANDLE hFile, //標(biāo)識(shí)通訊端口的句柄 LPDCB lpDCB //指向一個(gè)設(shè)備控制塊(DCB結(jié)構(gòu))的指針 ); SetCommState函數(shù)設(shè)置COM口的設(shè)備控制塊: BOOL SetCommState( HANDLE hFile, LPDCB lpDCB );除了在BCD中的設(shè)置外,程序一般還需要設(shè)置I/O緩沖區(qū)的大小和超時(shí)。Windows用I/O緩沖區(qū)來暫存串口輸入和輸出的數(shù)據(jù)。如果通信的速率較高,則應(yīng)該設(shè)置較大的緩沖區(qū)。調(diào)用SetupComm函數(shù)可以設(shè)置串行口的輸入和輸出緩沖區(qū)的大小。 BOOL SetupComm( HANDLE hFile, // 通信設(shè)備的句柄 DWORD dwInQueue, // 輸入緩沖區(qū)的大?。ㄗ止?jié)數(shù)) DWORD dwOutQueue // 輸出緩沖區(qū)的大小(字節(jié)數(shù)) );在用ReadFile和WriteFile讀寫串行口時(shí),需要考慮超時(shí)問題。超時(shí)的作用是在指定的時(shí)間內(nèi)沒有讀入或發(fā)送指定數(shù)量的字符,ReadFile或WriteFile的操作仍然會(huì)結(jié)束。 要查詢當(dāng)前的超時(shí)設(shè)置應(yīng)調(diào)用GetCommTimeouts函數(shù),該函數(shù)會(huì)填充一個(gè)COMMTIMEOUTS結(jié)構(gòu)。調(diào)用SetCommTimeouts可以用某一個(gè)COMMTIMEOUTS結(jié)構(gòu)的內(nèi)容來設(shè)置超時(shí)。 讀寫串口的超時(shí)有兩種:間隔超時(shí)和總超時(shí)。間隔超時(shí)是指在接收時(shí)兩個(gè)字符之間的最大時(shí)延??偝瑫r(shí)是指讀寫操作總共花費(fèi)的最大時(shí)間。寫操作只支持總超時(shí),而讀操作兩種超時(shí)均支持。用COMMTIMEOUTS結(jié)構(gòu)可以規(guī)定讀寫操作的超時(shí)。 COMMTIMEOUTS結(jié)構(gòu)的定義為: typedef struct _COMMTIMEOUTS { DWORD ReadIntervalTimeout; //讀間隔超時(shí) DWORD ReadTotalTimeoutMultiplier; //讀時(shí)間系數(shù) DWORD ReadTotalTimeoutConstant; //讀時(shí)間常量 DWORD WriteTotalTimeoutMultiplier; // 寫時(shí)間系數(shù) DWORD WriteTotalTimeoutConstant; //寫時(shí)間常量 } COMMTIMEOUTS,*LPCOMMTIMEOUTS;COMMTIMEOUTS結(jié)構(gòu)的成員都以毫秒為單位??偝瑫r(shí)的計(jì)算公式是: 總超時(shí)=時(shí)間系數(shù)×要求讀/寫的字符數(shù)+時(shí)間常量 例如,要讀入10個(gè)字符,那么讀操作的總超時(shí)的計(jì)算公式為: 讀總超時(shí)=ReadTotalTimeoutMultiplier×10+ReadTotalTimeoutConstant 可以看出:間隔超時(shí)和總超時(shí)的設(shè)置是不相關(guān)的,這可以方便通信程序靈活地設(shè)置各種超時(shí)。 如果所有寫超時(shí)參數(shù)均為0,那么就不使用寫超時(shí)。如果ReadIntervalTimeout為0,那么就不使用讀間隔超時(shí)。如果ReadTotalTimeoutMultiplier 和 ReadTotalTimeoutConstant 都為0,則不使用讀總超時(shí)。如果讀間隔超時(shí)被設(shè)置成MAXDWORD并且讀時(shí)間系數(shù)和讀時(shí)間常量都為0,那么在讀一次輸入緩沖區(qū)的內(nèi)容后讀操作就立即返回,而不管是否讀入了要求的字符。 在用重疊方式讀寫串口時(shí),雖然ReadFile和WriteFile在完成操作以前就可能返回,但超時(shí)仍然是起作用的。在這種情況下,超時(shí)規(guī)定的是操作的完成時(shí)間,而不是ReadFile和WriteFile的返回時(shí)間。 配置串口的示例代碼: SetupComm(hCom,1024,1024); //輸入緩沖區(qū)和輸出緩沖區(qū)的大小都是1024 COMMTIMEOUTS TimeOuts; //設(shè)定讀超時(shí) TimeOuts.ReadIntervalTimeout=1000; TimeOuts.ReadTotalTimeoutMultiplier=500; TimeOuts.ReadTotalTimeoutConstant=5000; //設(shè)定寫超時(shí) TimeOuts.WriteTotalTimeoutMultiplier=500; TimeOuts.WriteTotalTimeoutConstant=2000; SetCommTimeouts(hCom,&TimeOuts); //設(shè)置超時(shí) DCB dcb; GetCommState(hCom,&dcb); dcb.BaudRate=9600; //波特率為9600 dcb.ByteSize=8; //每個(gè)字節(jié)有8位 dcb.Parity=NOPARITY; //無奇偶校驗(yàn)位 dcb.StopBits=TWOSTOPBITS; //兩個(gè)停止位 SetCommState(hCom,&dcb); PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);在讀寫串口之前,還要用PurgeComm()函數(shù)清空緩沖區(qū),該函數(shù)原型: BOOL PurgeComm( HANDLE hFile, //串口句柄 DWORD dwFlags // 需要完成的操作 );參數(shù)dwFlags指定要完成的操作,可以是下列值的組合: PURGE_TXABORT 中斷所有寫操作并立即返回,即使寫操作還沒有完成。 PURGE_RXABORT 中斷所有讀操作并立即返回,即使讀操作還沒有完成。 PURGE_TXCLEAR 清除輸出緩沖區(qū) PURGE_RXCLEAR 清除輸入緩沖區(qū)(3)、讀寫串口 我們使用ReadFile和WriteFile讀寫串口,下面是兩個(gè)函數(shù)的聲明: BOOL ReadFile( HANDLE hFile, //串口的句柄 // 讀入的數(shù)據(jù)存儲(chǔ)的地址, // 即讀入的數(shù)據(jù)將存儲(chǔ)在以該指針的值為首地址的一片內(nèi)存區(qū) LPVOID lpBuffer, DWORD nNumberOfBytesToRead, // 要讀入的數(shù)據(jù)的字節(jié)數(shù) // 指向一個(gè)DWORD數(shù)值,該數(shù)值返回讀操作實(shí)際讀入的字節(jié)數(shù) LPDWORD lpNumberOfBytesRead, // 重疊操作時(shí),該參數(shù)指向一個(gè)OVERLAPPED結(jié)構(gòu),同步操作時(shí),該參數(shù)為NULL。 LPOVERLAPPED lpOverlapped ); BOOL WriteFile( HANDLE hFile, //串口的句柄 // 寫入的數(shù)據(jù)存儲(chǔ)的地址, // 即以該指針的值為首地址的nNumberOfBytesToWrite // 個(gè)字節(jié)的數(shù)據(jù)將要寫入串口的發(fā)送數(shù)據(jù)緩沖區(qū)。 LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, //要寫入的數(shù)據(jù)的字節(jié)數(shù) // 指向指向一個(gè)DWORD數(shù)值,該數(shù)值返回實(shí)際寫入的字節(jié)數(shù) LPDWORD lpNumberOfBytesWritten, // 重疊操作時(shí),該參數(shù)指向一個(gè)OVERLAPPED結(jié)構(gòu), // 同步操作時(shí),該參數(shù)為NULL。 LPOVERLAPPED lpOverlapped );在用ReadFile和WriteFile讀寫串口時(shí),既可以同步執(zhí)行,也可以重疊執(zhí)行。在同步執(zhí)行時(shí),函數(shù)直到操作完成后才返回。這意味著同步執(zhí)行時(shí)線程會(huì)被阻塞,從而導(dǎo)致效率下降。在重疊執(zhí)行時(shí),即使操作還未完成,這兩個(gè)函數(shù)也會(huì)立即返回,費(fèi)時(shí)的I/O操作在后臺(tái)進(jìn)行。 ReadFile和WriteFile函數(shù)是同步還是異步由CreateFile函數(shù)決定,如果在調(diào)用CreateFile創(chuàng)建句柄時(shí)指定了FILE_FLAG_OVERLAPPED標(biāo)志,那么調(diào)用ReadFile和WriteFile對該句柄進(jìn)行的操作就應(yīng)該是重疊的;如果未指定重疊標(biāo)志,則讀寫操作應(yīng)該是同步的。ReadFile和WriteFile函數(shù)的同步或者異步應(yīng)該和CreateFile函數(shù)相一致。 ReadFile函數(shù)只要在串口輸入緩沖區(qū)中讀入指定數(shù)量的字符,就算完成操作。而WriteFile函數(shù)不但要把指定數(shù)量的字符拷入到輸出緩沖區(qū),而且要等這些字符從串行口送出去后才算完成操作。 如果操作成功,這兩個(gè)函數(shù)都返回TRUE。需要注意的是,當(dāng)ReadFile和WriteFile返回FALSE時(shí),不一定就是操作失敗,線程應(yīng)該調(diào)用GetLastError函數(shù)分析返回的結(jié)果。例如,在重疊操作時(shí)如果操作還未完成函數(shù)就返回,那么函數(shù)就返回FALSE,而且GetLastError函數(shù)返回ERROR_IO_PENDING。這說明重疊操作還未完成。 同步方式讀寫串口比較簡單,下面先例舉同步方式讀寫串口的代碼: //同步讀串口 char str[100]; DWORD wCount;//讀取的字節(jié)數(shù) 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);在重疊操作時(shí),操作還未完成函數(shù)就返回。 重疊I/O非常靈活,它也可以實(shí)現(xiàn)阻塞(例如我們可以設(shè)置一定要讀取到一個(gè)數(shù)據(jù)才能進(jìn)行到下一步操作)。有兩種方法可以等待操作完成:一種方法是用象WaitForSingleObject這樣的等待函數(shù)來等待OVERLAPPED結(jié)構(gòu)的hEvent成員;另一種方法是調(diào)用GetOverlappedResult函數(shù)等待,后面將演示說明。 下面我們先簡單說一下OVERLAPPED結(jié)構(gòu)和GetOverlappedResult函數(shù): OVERLAPPED結(jié)構(gòu) OVERLAPPED結(jié)構(gòu)包含了重疊I/O的一些信息,定義如下: typedef struct _OVERLAPPED { // o DWORD Internal; DWORD InternalHigh; DWORD Offset; DWORD OffsetHigh; HANDLE hEvent; } OVERLAPPED;在使用ReadFile和WriteFile重疊操作時(shí),線程需要?jiǎng)?chuàng)建OVERLAPPED結(jié)構(gòu)以供這兩個(gè)函數(shù)使用。線程通過OVERLAPPED結(jié)構(gòu)獲得當(dāng)前的操作狀態(tài),該結(jié)構(gòu)最重要的成員是hEvent。hEvent是讀寫事件。當(dāng)串口使用異步通訊時(shí),函數(shù)返回時(shí)操作可能還沒有完成,程序可以通過檢查該事件得知是否讀寫完畢。 當(dāng)調(diào)用ReadFile, WriteFile 函數(shù)的時(shí)候,該成員會(huì)自動(dòng)被置為無信號狀態(tài);當(dāng)重疊操作完成后,該成員變量會(huì)自動(dòng)被置為有信號狀態(tài)。 GetOverlappedResult函數(shù) BOOL GetOverlappedResult( HANDLE hFile, // 串口的句柄 // 指向重疊操作開始時(shí)指定的OVERLAPPED結(jié)構(gòu) LPOVERLAPPED lpOverlapped, // 指向一個(gè)32位變量,該變量的值返回實(shí)際讀寫操作傳輸?shù)淖止?jié)數(shù)。 LPDWORD lpNumberOfBytesTransferred, // 該參數(shù)用于指定函數(shù)是否一直等到重疊操作結(jié)束。 // 如果該參數(shù)為TRUE,函數(shù)直到操作結(jié)束才返回。 // 如果該參數(shù)為FALSE,函數(shù)直接返回,這時(shí)如果操作沒有完成, // 通過調(diào)用GetLastError()函數(shù)會(huì)返回ERROR_IO_INCOMPLETE。 BOOL bWait );該函數(shù)返回重疊操作的結(jié)果,用來判斷異步操作是否完成,它是通過判斷OVERLAPPED結(jié)構(gòu)中的hEvent是否被置位來實(shí)現(xiàn)的。 異步讀串口的示例代碼: 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函數(shù)返回FALSE { if(GetLastError()==ERROR_IO_PENDING) //GetLastError()函數(shù)返回ERROR_IO_PENDING,表明串口正在進(jìn)行讀操作 { WaitForSingleObject(m_osRead.hEvent,2000); //使用WaitForSingleObject函數(shù)等待,直到讀操作完成或延時(shí)已達(dá)到2秒鐘 //當(dāng)串口讀操作進(jìn)行完畢后,m_osRead的hEvent事件會(huì)變?yōu)橛行盘?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 函數(shù)進(jìn)行讀操作前,應(yīng)先使用ClearCommError函數(shù)清除錯(cuò)誤。ClearCommError函數(shù)的原型如下: BOOL ClearCommError( HANDLE hFile, // 串口句柄 LPDWORD lpErrors, // 指向接收錯(cuò)誤碼的變量 LPCOMSTAT lpStat // 指向通訊狀態(tài)緩沖區(qū) );該函數(shù)獲得通信錯(cuò)誤并報(bào)告串口的當(dāng)前狀態(tài),同時(shí),該函數(shù)清除串口的錯(cuò)誤標(biāo)志以便繼續(xù)輸入、輸出操作。 參數(shù)lpStat指向一個(gè)COMSTAT結(jié)構(gòu),該結(jié)構(gòu)返回串口狀態(tài)信息。 COMSTAT結(jié)構(gòu) COMSTAT結(jié)構(gòu)包含串口的信息,結(jié)構(gòu)定義如下: 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成員變量,該成員變量的值代表輸入緩沖區(qū)的字節(jié)數(shù)。 最后用PurgeComm函數(shù)清空串口的輸入輸出緩沖區(qū)。 這段代碼用WaitForSingleObject函數(shù)來等待OVERLAPPED結(jié)構(gòu)的hEvent成員,下面我們再演示一段調(diào)用GetOverlappedResult函數(shù)等待的異步讀串口示例代碼: 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); bReadStatus=ReadFile(hCom, lpInBuffer,dwBytesRead, &dwBytesRead,&m_osRead); if(!bReadStatus) //如果ReadFile函數(shù)返回FALSE { if(GetLastError()==ERROR_IO_PENDING) { GetOverlappedResult(hCom, &m_osRead,&dwBytesRead,TRUE); // GetOverlappedResult函數(shù)的最后一個(gè)參數(shù)設(shè)為TRUE, //函數(shù)會(huì)一直等待,直到讀操作完成或由于錯(cuò)誤而返回。 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;(4)、關(guān)閉串口 利用API函數(shù)關(guān)閉串口非常簡單,只需使用CreateFile函數(shù)返回的句柄作為參數(shù)調(diào)用CloseHandle即可: BOOL CloseHandle( HANDLE hObject; //handle to object to close );串口編程的一個(gè)實(shí)例 為了讓您更好地理解串口編程,下面我們分別編寫兩個(gè)例程(見附帶的源碼部分),這兩個(gè)例程都實(shí)現(xiàn)了工控機(jī)與百特顯示儀表通過RS485接口進(jìn)行的串口通信。其中第一個(gè)例程采用同步串口操作,第二個(gè)例程采用異步串口操作。 例程1 打開VC++6.0,新建基于對話框的工程RS485Comm,在主對話框窗口IDD_RS485COMM_DIALOG上添加兩個(gè)按鈕,ID分別為IDC_SEND和IDC_RECEIVE,標(biāo)題分別為“發(fā)送”和“接收”;添加一個(gè)靜態(tài)文本框IDC_DISP,用于顯示串口接收到的內(nèi)容。 HANDLE hCom; //全局變量,串口句柄在RS485CommDlg.cpp文件中的OnInitDialog()函數(shù)添加如下代碼: // TODO: Add extra initialization here hCom=CreateFile("COM1",//COM1口 GENERIC_READ|GENERIC_WRITE, //允許讀和寫 0, //獨(dú)占方式 NULL, OPEN_EXISTING, //打開而不是創(chuàng)建 0, //同步方式 NULL); if(hCom==(HANDLE)-1) { AfxMessageBox("打開COM失敗!"); return FALSE; } SetupComm(hCom,100,100); //輸入緩沖區(qū)和輸出緩沖區(qū)的大小都是1024 COMMTIMEOUTS TimeOuts; //設(shè)定讀超時(shí) TimeOuts.ReadIntervalTimeout=MAXDWORD; TimeOuts.ReadTotalTimeoutMultiplier=0; TimeOuts.ReadTotalTimeoutConstant=0; //在讀一次輸入緩沖區(qū)的內(nèi)容后讀操作就立即返回, //而不管是否讀入了要求的字符。 //設(shè)定寫超時(shí) TimeOuts.WriteTotalTimeoutMultiplier=100; TimeOuts.WriteTotalTimeoutConstant=500; SetCommTimeouts(hCom,&TimeOuts); //設(shè)置超時(shí) DCB dcb; GetCommState(hCom,&dcb); dcb.BaudRate=9600; //波特率為9600 dcb.ByteSize=8; //每個(gè)字節(jié)有8位 dcb.Parity=NOPARITY; //無奇偶校驗(yàn)位 dcb.StopBits=TWOSTOPBITS; //兩個(gè)停止位 SetCommState(hCom,&dcb); PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);分別雙擊IDC_SEND按鈕和IDC_RECEIVE按鈕,添加兩個(gè)按鈕的響應(yīng)函數(shù): void CRS485CommDlg::OnSend() { // TODO: Add your control notification handler code here // 在此需要簡單介紹百特公司XMA5000的通訊協(xié)議: //該儀表RS485通訊采用主機(jī)廣播方式通訊。 //串行半雙工,幀11位,1個(gè)起始位(0),8個(gè)數(shù)據(jù)位,2個(gè)停止位(1) //如:讀儀表顯示的瞬時(shí)值,主機(jī)發(fā)送:DC1 AAA BB ETX //其中:DC1是標(biāo)準(zhǔn)ASCII碼的一個(gè)控制符號,碼值為11H(十進(jìn)制的17) //在XMA5000的通訊協(xié)議中,DC1表示讀瞬時(shí)值 //AAA是從機(jī)地址碼,也就是XMA5000顯示儀表的通訊地址 //BB為通道號,讀瞬時(shí)值時(shí)該值為01 //ETX也是標(biāo)準(zhǔn)ASCII碼的一個(gè)控制符號,碼值為03H //在XMA5000的通訊協(xié)議中,ETX表示主機(jī)結(jié)束符 char lpOutBuffer[7]; memset(lpOutBuffer,''\0'',7); //前7個(gè)字節(jié)先清零 lpOutBuffer[0]=''\x11''; //發(fā)送緩沖區(qū)的第1個(gè)字節(jié)為DC1 lpOutBuffer[1]=''0''; //第2個(gè)字節(jié)為字符0(30H) lpOutBuffer[2]=''0''; //第3個(gè)字節(jié)為字符0(30H) lpOutBuffer[3]=''1''; // 第4個(gè)字節(jié)為字符1(31H) lpOutBuffer[4]=''0''; //第5個(gè)字節(jié)為字符0(30H) lpOutBuffer[5]=''1''; //第6個(gè)字節(jié)為字符1(31H) lpOutBuffer[6]=''\x03''; //第7個(gè)字節(jié)為字符ETX //從該段代碼可以看出,儀表的通訊地址為001 DWORD dwBytesWrite=7; COMSTAT ComStat; DWORD dwErrorFlags; BOOL bWriteStat; ClearCommError(hCom,&dwErrorFlags,&ComStat); bWriteStat=WriteFile(hCom,lpOutBuffer,dwBytesWrite,& dwBytesWrite,NULL); if(!bWriteStat) { AfxMessageBox("寫串口失敗!"); } } void CRS485CommDlg::OnReceive() { // TODO: Add your control notification handler code here char str[100]; memset(str,''\0'',100); DWORD wCount=100;//讀取的字節(jié)數(shù) BOOL bReadStat; bReadStat=ReadFile(hCom,str,wCount,&wCount,NULL); if(!bReadStat) AfxMessageBox("讀串口失敗!"); PurgeComm(hCom, PURGE_TXABORT| PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR); m_disp=str; UpdateData(FALSE); }您可以觀察返回的字符串,其中有和儀表顯示值相同的部分,您可以進(jìn)行相應(yīng)的字符串操作取出儀表的顯示值。 打開ClassWizard,為靜態(tài)文本框IDC_DISP添加CString類型變量m_disp,同時(shí)添加WM_CLOSE的相應(yīng)函數(shù): void CRS485CommDlg::OnClose() { // TODO: Add your message handler code here and/or call default CloseHandle(hCom); //程序退出時(shí)關(guān)閉串口 CDialog::OnClose(); }程序的相應(yīng)部分已經(jīng)在代碼內(nèi)部作了詳細(xì)介紹。連接好硬件部分,編譯運(yùn)行程序,細(xì)心體會(huì)串口同步操作部分。 例程2 打開VC++6.0,新建基于對話框的工程RS485Comm,在主對話框窗口IDD_RS485COMM_DIALOG上添加兩個(gè)按鈕,ID分別為IDC_SEND和IDC_RECEIVE,標(biāo)題分別為“發(fā)送”和“接收”;添加一個(gè)靜態(tài)文本框IDC_DISP,用于顯示串口接收到的內(nèi)容。在RS485CommDlg.cpp文件中添加全局變量: HANDLE hCom; //全局變量, 串口句柄在RS485CommDlg.cpp文件中的OnInitDialog()函數(shù)添加如下代碼: hCom=CreateFile("COM1",//COM1口 GENERIC_READ|GENERIC_WRITE, //允許讀和寫 0, //獨(dú)占方式 NULL, OPEN_EXISTING, //打開而不是創(chuàng)建 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //重疊方式 NULL); if(hCom==(HANDLE)-1) { AfxMessageBox("打開COM失敗!"); return FALSE; } SetupComm(hCom,100,100); //輸入緩沖區(qū)和輸出緩沖區(qū)的大小都是100 COMMTIMEOUTS TimeOuts; //設(shè)定讀超時(shí) TimeOuts.ReadIntervalTimeout=MAXDWORD; TimeOuts.ReadTotalTimeoutMultiplier=0; TimeOuts.ReadTotalTimeoutConstant=0; //在讀一次輸入緩沖區(qū)的內(nèi)容后讀操作就立即返回, //而不管是否讀入了要求的字符。 //設(shè)定寫超時(shí) TimeOuts.WriteTotalTimeoutMultiplier=100; TimeOuts.WriteTotalTimeoutConstant=500; SetCommTimeouts(hCom,&TimeOuts); //設(shè)置超時(shí) DCB dcb; GetCommState(hCom,&dcb); dcb.BaudRate=9600; //波特率為9600 dcb.ByteSize=8; //每個(gè)字節(jié)有8位 dcb.Parity=NOPARITY; //無奇偶校驗(yàn)位 dcb.StopBits=TWOSTOPBITS; //兩個(gè)停止位 SetCommState(hCom,&dcb); PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);分別雙擊IDC_SEND按鈕和IDC_RECEIVE按鈕,添加兩個(gè)按鈕的響應(yīng)函數(shù): void CRS485CommDlg::OnSend() { // TODO: Add your control notification handler code here OVERLAPPED m_osWrite; memset(&m_osWrite,0,sizeof(OVERLAPPED)); m_osWrite.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); char lpOutBuffer[7]; memset(lpOutBuffer,''\0'',7); lpOutBuffer[0]=''\x11''; lpOutBuffer[1]=''0''; lpOutBuffer[2]=''0''; lpOutBuffer[3]=''1''; lpOutBuffer[4]=''0''; lpOutBuffer[5]=''1''; lpOutBuffer[6]=''\x03''; DWORD dwBytesWrite=7; COMSTAT ComStat; DWORD dwErrorFlags; BOOL bWriteStat; ClearCommError(hCom,&dwErrorFlags,&ComStat); bWriteStat=WriteFile(hCom,lpOutBuffer, dwBytesWrite,& dwBytesWrite,&m_osWrite); if(!bWriteStat) { if(GetLastError()==ERROR_IO_PENDING) { WaitForSingleObject(m_osWrite.hEvent,1000); } } } void CRS485CommDlg::OnReceive() { // TODO: Add your control notification handler code here OVERLAPPED m_osRead; memset(&m_osRead,0,sizeof(OVERLAPPED)); m_osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); COMSTAT ComStat; DWORD dwErrorFlags; char str[100]; memset(str,''\0'',100); DWORD dwBytesRead=100;//讀取的字節(jié)數(shù) BOOL bReadStat; ClearCommError(hCom,&dwErrorFlags,&ComStat); dwBytesRead=min(dwBytesRead, (DWORD)ComStat.cbInQue); bReadStat=ReadFile(hCom,str, dwBytesRead,&dwBytesRead,&m_osRead); if(!bReadStat) { if(GetLastError()==ERROR_IO_PENDING) //GetLastError()函數(shù)返回ERROR_IO_PENDING,表明串口正在進(jìn)行讀操作 { WaitForSingleObject(m_osRead.hEvent,2000); //使用WaitForSingleObject函數(shù)等待,直到讀操作完成或延時(shí)已達(dá)到2秒鐘 //當(dāng)串口讀操作進(jìn)行完畢后,m_osRead的hEvent事件會(huì)變?yōu)橛行盘?} } PurgeComm(hCom, PURGE_TXABORT| PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR); m_disp=str; UpdateData(FALSE); }打開ClassWizard,為靜態(tài)文本框IDC_DISP添加CString類型變量m_disp,同時(shí)添加WM_CLOSE的相應(yīng)函數(shù): void CRS485CommDlg::OnClose() { // TODO: Add your message handler code here and/or call default CloseHandle(hCom); //程序退出時(shí)關(guān)閉串口 CDialog::OnClose(); }您可以仔細(xì)對照這兩個(gè)例程,細(xì)心體會(huì)串口同步操作和異步操作的區(qū)別。 好了,就到這吧,祝您好運(yùn)。 |
|