1.開(kāi)啟一個(gè) Serial Port:
利用一般開(kāi)啟檔案的 CreatFile() 即可開(kāi)啟 serial port device用 CreateFile() API.
HANDLE CreateFile(
LPCTSTR lpFileName, // pointer to name of the file
DWORD dwDesiredAccess, // access (read-write) mode
DWORD dwShareMode, // share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes
DWORD dwCreationDistribution, // how to create
DWORD dwFlagsAndAttributes, // file attributes
HANDLE hTemplateFile // handle to file with attributes to copy
);

lpFileName 為 "COM1" 或是 "COM2"
dwDersiredAccess 一般為 GENERIC_READ|GENERIC_WRITE
dwShareMode "必須"為 0, 即不能共享, 但同一個(gè) process 中的不同 thread 在一開(kāi)啟之后就可以共享.
lpSecurityAttributes 一般為 NULL
dwCreateionDistributon 在這里"必須"為 OPEN_EXISTING
dwFlagsAndAttributes 定義了開(kāi)啟的屬性, 若是設(shè)成 FILE_FLAG_OVERLAPPED 則可使用異步的 I/O.
hTemplateFile "必須"為 NULL
傳回檔案 handle
設(shè)定 Serial Port 傳送及接收緩沖區(qū)的大小
在開(kāi)啟完 serial port 之后, 可以藉由呼叫 SetupComm() 來(lái)進(jìn)行配置傳送時(shí)的緩沖區(qū)及接收時(shí)的緩沖區(qū). 如果沒(méi)有呼叫 SetupComm() 的話, Win95 會(huì)配置內(nèi)定的緩沖區(qū)
BOOL SetupComm(
HANDLE hFile, // handle of communications device
DWORD dwInQueue, // size of input buffer
DWORD dwOutQueue // size of output buffer
);
2.關(guān)閉 Serial Port file
利用一般的 CloseHandle() 即可.
BOOL CloseHandle(
HANDLE hObject // handle to object to close
)
3.取得 Seial Port 的信息
在 Win32 里頭, 將一些通訊時(shí)會(huì)用到的信息用 COMMPROP 這個(gè)結(jié)構(gòu)來(lái)表示. (當(dāng)然不僅僅是 Serial Port) 可以用 GetCommProperties() 來(lái)取得:
BOOL GetCommProperties(
HANDLE hFile, // handle of communications device
LPCOMMPROP lpCommProp // address of communications properties structure
);

COMMPROP 長(zhǎng)的像這個(gè)樣子:

typedef struct _COMMPROP
{ // cmmp
WORD wPacketLength; // packet size, in bytes
WORD wPacketVersion; // packet version
DWORD dwServiceMask; // services implemented
DWORD dwReserved1; // reserved
DWORD dwMaxTxQueue; // max Tx bufsize, in bytes
DWORD dwMaxRxQueue; // max Rx bufsize, in bytes
DWORD dwMaxBaud; // max baud rate, in bps
DWORD dwProvSubType; // specific provider type
DWORD dwProvCapabilities; // capabilities supported
DWORD dwSettableParams; // changable parameters
DWORD dwSettableBaud; // allowable baud rates
WORD wSettableData; // allowable byte sizes
WORD wSettableStopParity; // stop bits/parity allowed
DWORD dwCurrentTxQueue; // Tx buffer size, in bytes
DWORD dwCurrentRxQueue; // Rx buffer size, in bytes
DWORD dwProvSpec1; // provider-specific data
DWORD dwProvSpec2; // provider-specific data
WCHAR wcProvChar[1]; // provider-specific data
} COMMPROP;
在這里, lpCommProp 需要 programmer 自行配置空間. 有趣的問(wèn)題是, 系統(tǒng)在這個(gè)結(jié)構(gòu)之后會(huì)需要額外的空間. 但是配置者也就是 programmer 卻不知道系統(tǒng)會(huì)需要多少. 很簡(jiǎn)單的做法是配置一大塊絕對(duì)會(huì)夠的空間. 另一個(gè)聰明的做法是執(zhí)行兩次 GetCommProperties() , 第一次只配置 sizeof(COMMPROP) 這么大的空間, 因?yàn)檫€沒(méi)有開(kāi)始執(zhí)行一些動(dòng)作, 所以系統(tǒng)并不會(huì)嘗試著在后面填東西, 所以不會(huì)出問(wèn)題. 接著執(zhí)行第一次的 GetCommProperties(), 得到結(jié)果, 取出結(jié)構(gòu)中的 wPacketLength, 這個(gè) member 代表實(shí)際上需要的大小, 然后依據(jù)這個(gè)大小重新配置一個(gè)新的. 這樣的話 , 就不會(huì)有浪費(fèi)任何空間的問(wèn)題了.
至于上述 COMMPROP 結(jié)構(gòu)的成員所代表的意思, on-line help 中應(yīng)該寫(xiě)的都滿清楚的 .
4.設(shè)定及取得通訊狀態(tài)
你可以利用 COMMPROP 來(lái)取得一些狀態(tài), 但是當(dāng)你想改變目前的設(shè)定時(shí)你需要兩個(gè) API 來(lái)完成:
BOOL GetCommState(
HANDLE hFile, // handle of communications device
LPDCB lpDCB // address of device-control block structure
);

BOOL SetCommState(
HANDLE hFile, // handle of communications device
LPDCB lpDCB // address of device-control block structure
);
你可以用 GetCommState() 來(lái)取得目前 Serial Port 的狀態(tài), 也可以用 SetCommState() 來(lái)設(shè)定 Serial Port 的狀態(tài).
DCB 的結(jié)構(gòu)就請(qǐng)自行翻閱 help 啰.
另外, programmer 最常控制的幾個(gè)設(shè)定就是 baud rate, parity method, data bits, 還有 stop bit. BuildCommDCB() 提供了對(duì)于這幾個(gè)常見(jiàn)設(shè)定的控制.
BOOL BuildCommDCB(
LPCTSTR lpDef, // pointer to device-control string
LPDCB lpDCB // pointer to device-control block
);

lpDef 長(zhǎng)的像這樣: "baud=2400 parity=N data=8 stop=1"
5.通訊設(shè)定對(duì)話盒
Win32 API 中提供了一個(gè)開(kāi)啟通訊設(shè)定對(duì)話盒的 API: CommConfigDialog(), 當(dāng)呼叫這個(gè) API 時(shí), 會(huì)蹦現(xiàn)一個(gè)可供設(shè)定 Baud Rate, Data Bits, Parity .. 等信息的對(duì)話盒, programmer 可以利用它來(lái)讓使用者設(shè)定一些信息, 并且取得結(jié)果.
BOOL CommConfigDialog(
LPTSTR lpszName, // pointer to device name string
HWND hWnd, // handle to window
LPCOMMCONFIG lpCC // pointer to comm. configuration structure
);
其中 lpCC 被用來(lái)存放設(shè)定值的結(jié)果.

typedef struct _COMM_CONFIG
{
DWORD dwSize;
WORD wVersion;
WORD wReserved;
DCB dcb;
DWORD dwProviderSubType;
DWORD dwProviderOffset;
DWORD dwProviderSize;
WCHAR wcProviderData[1];
} COMMCONFIG, *LPCOMMCONFIG;
在我們呼叫 CommConfigDialog() 之前, dwSize 要設(shè)為 sizeof(COMMCONFIG), wVersion 的值在這邊似乎不重要(我不清楚, VC5 的 on-line help 說(shuō)可以設(shè)為 1, 我手中的 book 的范例是設(shè)為 0x100), 呼叫完 CommConfigDialog() 之后, 成員 dcb 中的 BaudRate, ByteSize, StopBits, Parity 就是使用者的設(shè)定.
6.Timeout 的機(jī)制
因?yàn)閭鬏敃r(shí)并不會(huì)維持一個(gè)絕對(duì)穩(wěn)定的速率. 因?yàn)閭鬏斊焚|(zhì)的關(guān)系, programer 會(huì)需要 timeout 的機(jī)制來(lái)協(xié)助他們做一些控制. 在 Win32 通訊 Timeout 的機(jī)制中, timeout 的性質(zhì)共分為兩類(lèi), 先來(lái)看看 COMMTIMEOUTS 這個(gè)結(jié)構(gòu):

typedef struct _COMMTIMEOUTS
{ // ctmo
DWORD ReadIntervalTimeout;
DWORD ReadTotalTimeoutMultiplier;
DWORD ReadTotalTimeoutConstant;
DWORD WriteTotalTimeoutMultiplier;
DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
programmer 可以利用 GetCommTimeouts() 和 SetCommTimeouts() 來(lái)讀取或是設(shè)定目前的 timeout 值.
BOOL GetCommTimeouts(
HANDLE hFile, // handle of communications device
LPCOMMTIMEOUTS lpCommTimeouts // address of comm. time-outs structure
);

BOOL SetCommTimeouts(
HANDLE hFile, // handle of communications device
LPCOMMTIMEOUTS lpCommTimeouts // address of communications time-out structure
);
第一種 timeout 的機(jī)制稱(chēng)為 interval timeout, 從字面上的意義很容易可以理解這種 timeout 的機(jī)制是讀取字符之間的間隔時(shí)間的 timeout, 只有讀取字符時(shí)才能夠使用interval timeout. 也就是在這個(gè)結(jié)構(gòu)中的 ReadIntervalTimeout, 單位為 ms, 當(dāng)讀取完一個(gè)字符后, 超過(guò)了 ReadIntervalTimeout 的值, 卻還沒(méi)有讀到下一個(gè)字符時(shí), timeout 就發(fā)生了.
第二種 timeout 的機(jī)制稱(chēng)為 total timeout, 顧名思義即是傳輸?shù)目倳r(shí)間的 timeout . 在這種 timeout 的機(jī)制下, Win32 提供了一個(gè)具有彈性的方式來(lái)設(shè)定 total timeout. 以讀取的 total timeout 為例, 利用 ReadTotalTimeoutMultiplier 和 ReadTotalTimeoutConstant 構(gòu)成了一個(gè)線性的上限值. 什么意思呢? 實(shí)際上的 total timeout 應(yīng)該是這樣的一個(gè)式子:
ReadTotalTimeout = ReadTotalTimeOutMultiplier * BytesToRead + ReadTotalTimeoutConstant
WriteTotalTimeout 用同樣的公式來(lái)計(jì)算. 這樣的話, 不僅可以用一個(gè)固定的值來(lái)做為 timeout 值, 也可以用條線來(lái)做為 timeout 的值, 而隨著要讀取或是要寫(xiě)的 bytes 數(shù)而變動(dòng).
如果不想使用 timeout, 就把 COMMTIMEOUTS 里頭的資料成員都填為 0.
如果你將 ReadIntervalTimeout 設(shè)為 MAXDWORD, 且將 ReadTotalTimeOutMultiplier 和 ReadTotalTimeoutConstant 都設(shè)為 0 的話, 那么讀取時(shí), 如果 receive queue 里頭并沒(méi)有資料, 讀取的動(dòng)作將會(huì)馬上返回, 而不會(huì)停滯在讀取的動(dòng)作.
這里有一個(gè)和 BuildCommDCB() 很像的 API 叫 BuildCommDCBAndTimeouts():
BOOL BuildCommDCBAndTimeouts(
LPCTSTR lpDef, // pointer to the device-control string
LPDCB lpDCB, // pointer to the device-control block
LPCOMMTIMEOUTS lpCommTimeouts // pointer to comm. time-out structure
);
lpDef 一樣是控制字符串, 可以給像 BuildCommDCB() 中的 lpDef 那樣格式的字符串, 但是多了 "TO=XXX" 這個(gè)設(shè)定. 如果 "TO=ON", 這個(gè) API 會(huì)依據(jù) lpCommTimeouts 里頭的值來(lái)設(shè)定讀和寫(xiě)的 timeout 值. 如果 "TO=OFF", 則會(huì)設(shè)定這個(gè) device 沒(méi)有 timeout. 如果是 "ON" 和 "OFF" 之外的其它值, 則 lpCommTimeouts 的設(shè)定將會(huì)被忽略.
對(duì)了, 在設(shè)定完 timeout 值之后, 記得要檢查 COMMPROP 里的 dwProvCapabilities 中的 PCF_INTTIMEOUTS 和 PCF_TOTALTIMEOUTS 兩個(gè) flags 是否有被 set, 以確認(rèn) interval timeout 和 total timeout 是否有支持.
7.讀取資料
從 serial port 里頭讀取資料就跟讀取一般的檔案一樣, 使用 ReadFile() 來(lái)達(dá)成.
BOOL ReadFile(
HANDLE hFile, // handle of file to read
LPVOID lpBuffer, // address of buffer that receives data
DWORD nNumberOfBytesToRead, // number of bytes to read
LPDWORD lpNumberOfBytesRead, // address of number of bytes read
LPOVERLAPPED lpOverlapped // address of structure for data
);
要注意的是, nNumberOfBytesToRead 設(shè)定的是一次最多的讀取量, 很有可能所讀取的值(檢查 lpNumberOfBytesRead)小于這個(gè)值. 通常在錯(cuò)誤發(fā)生或是 timeout 發(fā)生時(shí)這個(gè) API 就會(huì)返回.
PurgeComm() 這個(gè) API 可以用來(lái)終止目前正在進(jìn)行的讀或?qū)懙膭?dòng)作, 也可以 flush 掉 I/O buffer 內(nèi)等待讀或?qū)懙馁Y料.
BOOL PurgeComm(
HANDLE hFile, // handle of communications resource
DWORD dwFlags // action to perform
);
其中 dwFlags 共有四種 flags:
PURGE_TXABORT: 終止目前正在進(jìn)行的(背景)寫(xiě)入動(dòng)作
PURGE_RXABORT: 終正目前正在進(jìn)行的(背景)讀取動(dòng)作
PURGE_TXCLEAR: flush 寫(xiě)入的 buffer
PURGE_RXCLEAR: flush 讀取的 buffer
而使用 FlushFileBuffers() 可以確保所有的資料都被送出, 這個(gè) API 才會(huì)返回.
另外一個(gè)有趣的 API 是 ClearCommError(), 從字面上的意思看來(lái), 它是用來(lái)清除錯(cuò)誤情況用的, 但是實(shí)際上它還可以拿來(lái)取得目前通訊設(shè)備的一些信息.
BOOL ClearCommError(
HANDLE hFile, // handle to communications device
LPDWORD lpErrors, // pointer to variable to receive error codes
LPCOMSTAT lpStat // pointer to buffer for communications status
);
呼叫這個(gè) API 之后, 關(guān)于通訊設(shè)備的一些信息會(huì)被儲(chǔ)存在 lpStat 中, COMSTAT 的結(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
藉由 fCtsHold, fDsrHold, fRlsdHold, fXoffHold, fXoffSent 可以知道目前因?yàn)槭裁匆蛩囟雇ㄓ嵶璧K住了.( 跟 handshaking 和 flow control 有關(guān)) cbInque 和 cbOutQue 則可以顯示出還有多少 bytes 在讀取或是寫(xiě)入 queue 中.
8.寫(xiě)入資料
和讀取資料一樣, programmer 可以使用 WriteFile() 來(lái)將資料寫(xiě)入 serial port.
BOOL WriteFile(
HANDLE hFile, // handle to file to write to
LPCVOID lpBuffer, // pointer to data to write to file
DWORD nNumberOfBytesToWrite, // number of bytes to write
LPDWORD lpNumberOfBytesWritten, // pointer to number of bytes written
LPOVERLAPPED lpOverlapped // pointer to structure needed for overlapped I/O
);
關(guān)于通訊設(shè)備的寫(xiě)入有三個(gè)很有趣的 API, 它們分別是 SetCommBreak(), ClearCommBreak, 和 TransmitCommChar().
BOOL SetCommBreak(
HANDLE hFile // handle of communications device
);

BOOL ClearCommBreak(
HANDLE hFile // handle to communications device
);

BOOL TransmitCommChar(
HANDLE hFile, // handle of communications device
char cChar // character to transmit
);
SetCommBreak() 是用來(lái)暫停目前的傳輸作業(yè), 它會(huì)使 buffer 中的資料都不再被送出, 這個(gè)時(shí)候, program 可以去做些雜七雜八的事, 之后, 再利用 ClearCommBreak() 回復(fù)傳輸作業(yè).
TransmitCommChar() 是用來(lái)立即性的趕在所有 buffer 數(shù)據(jù)被送出去之前, 傳輸一個(gè)字符的數(shù)據(jù)出去, 即使 buffer 中還有資料. 換句話說(shuō), 這個(gè)字符有最高的優(yōu)先權(quán)被送出去.
9.事件驅(qū)動(dòng)式的 I/O
在 Win32 里頭, 對(duì)于通訊設(shè)備的 I/O 可以用像是事件驅(qū)動(dòng)式的方法來(lái)達(dá)成. 主要是利用一個(gè)叫 WaitCommEvent() 的 API. 呼叫這個(gè) API 之后, 會(huì)一直 block 到設(shè)定的事件發(fā)生之后才會(huì)返回. 我們先來(lái)看看如何設(shè)定事件, 再回過(guò)頭來(lái)看 WaitCommEvent() .
programer 可以用 GetCommMask() 和 SetCommMask() 來(lái)取得或是設(shè)定目前設(shè)定的通訊事件.
BOOL GetCommMask(
HANDLE hFile, // handle of communications device
LPDWORD lpEvtMask // address of variable to get event mask
);

BOOL SetCommMask(
HANDLE hFile, // handle of communications device
DWORD dwEvtMask // mask that identifies enabled events
);
可以設(shè)定的事件有 EV_BREAK, EV_CTS, EV_DSR, EV_ERR, EV_RING, EV_RLSD, EV_RXCHAR, EV_RXFLAG, EV_TXEMPTY.(其意義請(qǐng)自行參考 help), 當(dāng)然, 你可以把它們 or 起來(lái)成為組合的事件.
在設(shè)定完想要處理的事件之后, 可以使用 WaitCommEvent()
BOOL WaitCommEvent(
HANDLE hFile, // handle of communications device
LPDWORD lpEvtMask, // address of variable for event that occurred
LPOVERLAPPED lpOverlapped, // address of overlapped structure
);
WaitCommEvent() 會(huì)一直 block 到你所設(shè)定的通訊事件發(fā)生為止. 所以當(dāng) WaitCommEvent() 返回時(shí), 你可以由 lpEvtMask 取得究竟是那一事件發(fā)生, 再來(lái)決定要如何處理.
舉例來(lái)說(shuō), 可以用 SetCommMask() 設(shè)定事件為 EV_RXCHAR, 那么在呼叫 WaitCommEvent() 時(shí), 它會(huì)等到有字符可供讀取時(shí)才會(huì)返回, 那么在它返回之后, 可以檢查一下 lpEvtMask 中是否 set 了 EV_RXCHAR, 如果是的話就可以用 ReadFile() 去讀取. 這樣的話, 可以避免掉某些情形之下, 需要做 polling 所引起效率不彰的問(wèn)題.
10.錯(cuò)誤的處理
前面提過(guò)的 ClearnCommError() 可以用來(lái)取得目前發(fā)生錯(cuò)誤的原因.(請(qǐng)參見(jiàn) help)
11.硬件的控制命令
Win32 中提供了 EscapeCommFunction() 允許 programer 對(duì)幾個(gè)硬件訊號(hào)做控制.
BOOL EscapeCommFunction(
HANDLE hFile, // handle to communications device
DWORD dwFunc // extended function to perform
);
其中 dwFunc 可以是:
CLRDTR : 讓 DTR OFF
CLRRTS : 讓 RTS OFF
SETDTR : 讓 DTR ON
SETRTS : 讓 RTS ON
SETXOFF : "仿真" 接收到 XOFF 字符
SETXON : "仿真" 接收到 XON 字符
SETBREAK : 和 SetCommBreak() 的意思相同
CLRBREAK : 和 ClearCommBreak() 的意思相同