日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Win32 API串口编程

發布時間:2023/12/10 编程问答 50 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Win32 API串口编程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在工業控制中,工控機(一般都基于Windows平臺)經常需要與智能儀表通過串口進行通信。串口通信方便易行,應用廣泛。
一般情況下,工控機和各智能儀表通過RS485總線進行通信。RS485的通信方式是半雙工的,只能由作為主節點的工控PC機依次輪詢網絡上的各智能控制單元子節點。每次通信都是由PC機通過串口向智能控制單元發布命令,智能控制單元在接收到正確的命令后作出應答。
  在Win32下,可以使用兩種編程方式實現串口通信,其一是使用ActiveX控件,這種方法程序簡單,但欠靈活。其二是調用Windows的API函數,這種方法可以清楚地掌握串口通信的機制,并且自由靈活。本文我們只介紹API串口通信部分。
  串口的操作可以有兩種操作方式:同步操作方式和重疊操作方式(又稱為異步操作方式)。同步操作時,API函數會阻塞直到操作完成以后才能返回(在多線程方式中,雖然不會阻塞主線程,但是仍然會阻塞監聽線程);而重疊操作方式,API函數會立即返回,操作在后臺進行,避免線程的阻塞。

無論那種操作方式,一般都通過四個步驟來完成:
(1) 打開串口
(2) 配置串口
(3) 讀寫串口
(4) 關閉串口

(1) 打開串口

  Win32系統把文件的概念進行了擴展。無論是文件、通信設備、命名管道、郵件槽、磁盤、還是控制臺,都是用API函數CreateFile來打開或創建的。該函數的原型為:

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方式打開串口的示例代碼:

?????? 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打開串口的示例代碼:

?????? 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位停止位

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????????? 256000

?

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,????? //串口的句柄
??? // 寫入的數據存儲的地址,
??? // 即以該指針的值為首地址的nNumberOfBytesToWrite
??? // 個字節的數據將要寫入串口的發送數據緩沖區。
??? 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);
?????? 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;

(4)、關閉串口

利用API函數關閉串口非常簡單,只需使用CreateFile函數返回的句柄作為參數調用CloseHandle即可:

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

串口編程的一個實例

為了讓您更好地理解串口編程,下面我們分別編寫兩個例程(見附帶的源碼部分),這兩個例程都實現了工控機與百特顯示儀表通過RS485接口進行的串口通信。其中第一個例程采用同步串口操作,第二個例程采用異步串口操作。
我們只介紹軟件部分,RS485接口接線方法不作介紹,感興趣的讀者可以查閱相關資料。

例程1

打開VC++6.0,新建基于對話框的工程RS485Comm,在主對話框窗口IDD_RS485COMM_DIALOG上添加兩個按鈕,ID分別為IDC_SEND和IDC_RECEIVE,標題分別為“發送”和“接收”;添加一個靜態文本框IDC_DISP,用于顯示串口接收到的內容。

在RS485CommDlg.cpp文件中添加全局變量:

HANDLE hCom; //全局變量,串口句柄

在RS485CommDlg.cpp文件中的OnInitDialog()函數添加如下代碼:

?????? // TODO: Add extra initialization here
?????? hCom=CreateFile("COM1",//COM1口
????????????? GENERIC_READ|GENERIC_WRITE, //允許讀和寫
????????????? 0, //獨占方式
????????????? NULL,
????????????? OPEN_EXISTING, //打開而不是創建
????????????? 0, //同步方式
????????????? NULL);
?????? if(hCom==(HANDLE)-1)
?????? {
????????????? AfxMessageBox("打開COM失敗!");
????????????? return FALSE;
?????? }
?????? SetupComm(hCom,100,100); //輸入緩沖區和輸出緩沖區的大小都是100
?????? COMMTIMEOUTS TimeOuts;

?????? //設定讀超時
?????? TimeOuts.ReadIntervalTimeout=MAXDWORD;
?????? TimeOuts.ReadTotalTimeoutMultiplier=0;
?????? TimeOuts.ReadTotalTimeoutConstant=0;

?????? //在讀一次輸入緩沖區的內容后讀操作就立即返回,
?????? //而不管是否讀入了要求的字符。

?????? //設定寫超時
?????? TimeOuts.WriteTotalTimeoutMultiplier=100;
?????? TimeOuts.WriteTotalTimeoutConstant=500;
?????? 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);

分別雙擊IDC_SEND按鈕和IDC_RECEIVE按鈕,添加兩個按鈕的響應函數:

?

void CRS485CommDlg::OnSend()
{
?????? // TODO: Add your control notification handler code here
?????? // 在此需要簡單介紹百特公司XMA5000的通訊協議:
?????? //該儀表RS485通訊采用主機廣播方式通訊。
?????? //串行半雙工,幀11位,1個起始位(0),8個數據位,2個停止位(1)
?????? //如:讀儀表顯示的瞬時值,主機發送:DC1 AAA BB ETX
?????? //其中:DC1是標準ASCII碼的一個控制符號,碼值為11H(十進制的17)
?????? //在XMA5000的通訊協議中,DC1表示讀瞬時值
?????? //AAA是從機地址碼,也就是XMA5000顯示儀表的通訊地址
?????? //BB為通道號,讀瞬時值時該值為01
?????? //ETX也是標準ASCII碼的一個控制符號,碼值為03H
?????? //在XMA5000的通訊協議中,ETX表示主機結束符
?????? char lpOutBuffer[7];
?????? memset(lpOutBuffer,''\0'',7); //前7個字節先清零
?????? lpOutBuffer[0]=''\x11''; //發送緩沖區的第1個字節為DC1
?????? lpOutBuffer[1]=''0''; //第2個字節為字符0(30H)
?????? lpOutBuffer[2]=''0''; //第3個字節為字符0(30H)
?????? lpOutBuffer[3]=''1''; // 第4個字節為字符1(31H)
?????? lpOutBuffer[4]=''0''; //第5個字節為字符0(30H)
?????? lpOutBuffer[5]=''1''; //第6個字節為字符1(31H)
?????? lpOutBuffer[6]=''\x03''; //第7個字節為字符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;//讀取的字節數
?????? 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);
?????
}

您可以觀察返回的字符串,其中有和儀表顯示值相同的部分,您可以進行相應的字符串操作取出儀表的顯示值。
打開ClassWizard,為靜態文本框IDC_DISP添加CString類型變量m_disp,同時添加WM_CLOSE的相應函數:
void CRS485CommDlg::OnClose()
{
?????? // TODO: Add your message handler code here and/or call default
??? CloseHandle(hCom);????? //程序退出時關閉串口
?????? CDialog::OnClose();
}

程序的相應部分已經在代碼內部作了詳細介紹。連接好硬件部分,編譯運行程序,細心體會串口同步操作部分。

例程2

打開VC++6.0,新建基于對話框的工程RS485Comm,在主對話框窗口IDD_RS485COMM_DIALOG上添加兩個按鈕,ID分別為IDC_SEND和IDC_RECEIVE,標題分別為“發送”和“接收”;添加一個靜態文本框IDC_DISP,用于顯示串口接收到的內容。在RS485CommDlg.cpp文件中添加全局變量:

?????? HANDLE hCom; //全局變量,

串口句柄在RS485CommDlg.cpp文件中的OnInitDialog()函數添加如下代碼:

?????? hCom=CreateFile("COM1",//COM1口
????????????? GENERIC_READ|GENERIC_WRITE, //允許讀和寫
????????????? 0, //獨占方式
????????????? NULL,
????????????? OPEN_EXISTING, //打開而不是創建
????????????? FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //重疊方式
????????????? NULL);

?????? if(hCom==(HANDLE)-1)
?????? {
????????????? AfxMessageBox("打開COM失敗!");
????????????? return FALSE;
?????? }

?????? SetupComm(hCom,100,100); //輸入緩沖區和輸出緩沖區的大小都是100
?????? COMMTIMEOUTS TimeOuts;

?????? //設定讀超時
?????? TimeOuts.ReadIntervalTimeout=MAXDWORD;
?????? TimeOuts.ReadTotalTimeoutMultiplier=0;
?????? TimeOuts.ReadTotalTimeoutConstant=0;
?????//在讀一次輸入緩沖區的內容后讀操作就立即返回,//而不管是否讀入了要求的字符。
?????? //設定寫超時
?????? TimeOuts.WriteTotalTimeoutMultiplier=100;
?????? TimeOuts.WriteTotalTimeoutConstant=500;
?????? 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);

分別雙擊IDC_SEND按鈕和IDC_RECEIVE按鈕,添加兩個按鈕的響應函數:

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;//讀取的字節數
?????? 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()函數返回ERROR_IO_PENDING,表明串口正在進行讀操作
????????????? {
???????????????????? WaitForSingleObject(m_osRead.hEvent,2000);
??????????? //使用WaitForSingleObject函數等待,直到讀操作完成或延時已達到2秒鐘
????????????????? //當串口讀操作進行完畢后,m_osRead的hEvent事件會變為有信號
????????????? }
?????? }
?????? PurgeComm(hCom, PURGE_TXABORT|
????????????? PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
?????? m_disp=str;
?????? UpdateData(FALSE);
}

打開ClassWizard,為靜態文本框IDC_DISP添加CString類型變量m_disp,同時添加WM_CLOSE的相應函數:

void CRS485CommDlg::OnClose()
{
????? // TODO: Add your message handler code here and/or call default
??? CloseHandle(hCom);????? //程序退出時關閉串口
?????? CDialog::OnClose();
}
您可以仔細對照這兩個例程,細心體會串口同步操作和異步操作的區別。 好了,就到這吧,祝您好運。

?

?

?

串口:枚舉串口四法

串口作為最基本的電腦通信?I/O?接口,其使用雖然在?PC?上越來越少,但是在工業儀器領域仍然用的相當普遍,由于筆者工作中需要用到串口,而且發現枚舉串口至今仍未搞得很清楚,為此自己先整理下,希望大俠和同行們對我不懂和錯誤的地方指點一下。

?

1?、查詢注冊表

查詢注冊表的方法是網上見到的比較常見的方法,該方法就是使用編程方法讀取注冊表內信息,相當于用戶通過在運行框內輸入?”regedit”(或?regedit32?)直接打開注冊表,查看“?HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM?”項來獲取串口信息。以下是源代碼:

CString???strSerialList[256];??//?臨時定義?256?個字符串組,因為系統最多也就?256?個

HKEY hKey;

LPCTSTR data_Set="HARDWARE\\DEVICEMAP\\SERIALCOMM\\";

long ret0 = (::RegOpenKeyEx(HKEY_LOCAL_MACHINE, data_Set, 0, KEY_READ, &hKey));

if(ret0 != ERROR_SUCCESS)

{

return -1;

}

int i = 0;

CHAR Name[25];

UCHAR szPortName[25];

LONG Status;

DWORD dwIndex = 0;

DWORD dwName;

DWORD dwSizeofPortName;

DWORD Type;

dwName = sizeof(Name);

dwSizeofPortName = sizeof(szPortName);

do

{

Status = RegEnumValue(hKey, dwIndex++, Name, &dwName, NULL, &Type,

??????szPortName, &dwSizeofPortName);

if((Status == ERROR_SUCCESS)||(Status == ERROR_MORE_DATA))

{

strSerialList[i] = CString(szPortName);???????//?串口字符串保存

i++;//?串口計數

???}

} while((Status == ERROR_SUCCESS)||(Status == ERROR_MORE_DATA));

RegCloseKey(hKey);

以上方法同樣也可以實現對并口的查詢,只要將?"HARDWARE \\ DEVICEMAP\\ SERIALCOMM\\"?用?"HARDWARE\\DEVICEMAP\\PARALLEL PORTS\\"?代替就行了。

比較:該方法時間最省,筆者在自己電腦上試過,在?1ms?(少于?1ms?的我也不知道怎么編程計時)內即可完成;同時也可解決?usb?轉串口設備的問題,比較實用,唯一缺點是,如果用戶在裝某些軟硬件時在注冊表中注冊了虛擬串口之類的,用此法枚舉得到的該類串口實際上是不能當串口用的。

?

2?、使用?EnumPort?方法

該方法調用?EnumPort?()?API?函數,該函數本身就是枚舉電腦端口用的,它枚舉的并非只有串口,所以必須對其所得串口進行分析選擇,以下是源代碼:

???????int m_nSerialPortNum(0);//?串口計數

???????CString??????????strSerialList[256];??//?臨時定義?256?個字符串組

???????LPBYTE pBite??= NULL;

???????DWORD pcbNeeded = 0;??// bytes received or required

???????DWORD pcReturned = 0;??// number of ports received

???????m_nSerialPortNum = 0;

???????//?獲取端口信息,能得到端口信息的大小?pcbNeeded

???????EnumPorts(NULL, 2, pBite, 0, &pcbNeeded, &pcReturned);

???????pBite = new BYTE[pcbNeeded];

???????//?枚舉端口,能得到端口的具體信息?pBite?以及端口的的個數?pcReturned

???????EnumPorts(NULL, 2, pBite, pcbNeeded, &pcbNeeded, &pcReturned);

???????PORT_INFO_2 *pPort;

???????pPort = (PORT_INFO_2*)pBite;

???????for ( i = 0; i < pcReturned; i++)

???????{

??????????????CString str = pPort[i].pPortName;

??????????????//?串口信息的具體確定

??????????????if (str.Left(3) == "COM")

??????????????{??????????????????

?????????????????????strSerialList[m_nSerialPortNum] = str.Left(strlen(str) - 1);

?????????????????????//CString temp = str.Right(strlen(str) - 3);//?下面兩行注釋獲取串口序號用

?????????????????????//m_nSerialPortNo[m_nSerialPortNum] = atoi(temp.Left(strlen(temp) - 1));

?????????????????????m_nSerialPortNum++;????????????????

??????????????}

???????}

以上方法除了串口,還可以枚舉所有的并口和打印機等接口,而且能找到虛擬串口(這些串口有些未使用時,在注冊表和硬件設備管理器中是不能取得的)。但是該方法稍微耗時些,筆者在自己電腦上試過,大概需要幾十?ms?,主要問題是該方法有些?usb?串口并不能查到,所以該方法并不可靠。

?

3?、依次打開串口的方法

該方法就是中規中矩的依次打開串口,看打開是否成功來判斷串口的有無,該方法源代碼如下:

???????int m_nSerialPortNum(0);//?串口數

???????CString??????????strSerialList[256];??//?臨時定義?30?個字符串組

???????int nCom = 0;

???????int count = 0;

???????HANDLE hCom;

???????do {

??????????????nCom++;

??????????????strCom.Format("COM%d", nCom);

??????????????hCom = CreateFile(strCom, 0, 0, 0,

?????????????????????OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

??????????????if(INVALID_HANDLE_VALUE == hCom )

?????????????????????break;

??????????????strSerialList[m_nSerialPortNum] = strCom;

??????????????m_nSerialPortNum++;?????????

??????????????CloseHandle(hCom);

???????} while(1);

以上方法枚舉的都是當前可用的串口,如果有一個串口當前被占用則其后的串口也將無法枚舉得到,當然以上方法也可以改成調用?for?循環讓其枚舉打開?256?個串口的方法以避免上述情況,不過該方法比前兩種更耗時(一般查找一個串口就要?15ms?左右),不過可以枚舉得到所有當前可打開的串口,當然不能枚舉得到一些虛擬串口。

?

4?、使用?SetupAPI?函數集的方法

此種方法是我所見過最簡單的方法,之所以簡單是因為已經有人將復雜的代碼封裝起來了,我只需像傻子一樣調用就可以完成工作了,下面給出本人調用該方法的例子代碼:

???????int m_nSerialPortNum(0);//?串口計數

???????CString??????????strSerialList[256];??//?臨時定義?256?個字符串組

???????CArray<SSerInfo,SSerInfo&> asi;

???????EnumSerialPorts(asi,TRUE);//?參數為?TRUE?時枚舉當前可以打開的串口,?
//?否則枚舉所有串口

???????m_nSerialPortNum = asi.GetSize();

???????for (int i=0; i<asi.GetSize(); i++)

???????{

??????????????CString str = asi[i].strFrien dlyName;

???????}

補充說明一下,使用該方法只要在你的程序中,添加“?EnumSerial.cpp?”和“?EnumSerial.h?”兩個文件,并且將?Setupapi.lib?包含進你的工程文件中就行了,該方法時間上來說可能和第三種方法差不多,但該方法獲取的串口完完全全就是硬件設備管理器中的串口。

總結

以上是生活随笔為你收集整理的Win32 API串口编程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。