📄 -
字号:
@stCC).wVersion,100h<BR>mov (COMMCONFIG ptr
@stCC).dwProviderSubType,1<BR>invoke CommConfigDialog,addr
[esi].szPortName,[esi].hWnd,addr @stCC<BR>popad<BR>ret</P>
<P>_CommConfigDialog endp</P>
<P><FONT color=#ff0000>3 Win32 通讯 API Bug 之二---
BuildCommDCB</FONT></P>
<P>BuildCommDCB 的功能是把一个字符串如 com1:9600,n,8,1 这样的转换到具体的数据填写到 DCB
中,但使用中也存在问题,我发现我用它转换象 com1:9600,e,7,1 之类的带校验位的字符串,它总是无法把这个 e
给我转换过去,设置好串口一看,成了 9600,n,7,1,而上面提到的 CommConfigDialog
返回的结果用来设置串口却是正确的,经过比较,发现问题出在 DCB.fbits.fParity 这个 bit 上,只有把这个 bit 置
1,校验位才是有效的,而 BuildCommDCB 恰恰是漏了这个 bit,所有如果你要使用 BuildCommDCB,别忘了补充把
DCB.fbits.fParity 设置回去,我用的代码是:</P>
<P>_BuildCommDCB proc _lpszPara,_lpstDCB</P>
<P>pushad<BR>mov esi,_lpstDCB<BR>assume esi:ptr DCB</P>
<P>invoke RtlZeroMemory,esi,sizeof DCB<BR>invoke
BuildCommDCB,_lpszPara,esi<BR>;********************************************************************<BR>;
根据校验位补充设置 DCB 中的 DCB.fbits.fParity
字段<BR>;********************************************************************<BR>mov
dword ptr [esi].fbits,0010b</P>
<P>cld<BR>@@:<BR>lodsb<BR>or al,al<BR>jz @F<BR>cmp al,'='<BR>jz
_BCD_Check<BR>cmp al,','<BR>jnz @B<BR>_BCD_Check:<BR>lodsb<BR>or
al,al<BR>jz @F<BR>or al,20h<BR>cmp al,'n'<BR>jnz
@B<BR>;********************************************************************<BR>;
扫描到 =n 或 ,n
则取消校验位<BR>;********************************************************************<BR>mov
esi,_lpstDCB<BR>and dword ptr [esi].fbits,not
0010b<BR>@@:<BR>popad<BR>ret</P>
<P>_BuildCommDCB endp</P>
<P><FONT color=#ff0000>4 Win32 通讯编程的一般流程</FONT></P>
<P>由于同步方式相对比较简单,在这里讲述的是异步方式的流程,在其他的很多文章里提到了 Windows 通讯 API
有二十多个,它们是:</P>
<P>BuildCommDCB<BR>BuildCommDCBAndTimeouts<BR>ClearCommBreak<BR>ClearCommError<BR>CommConfigDialog<BR>EscapeCommFunction<BR>GetCommConfig<BR>GetCommMask<BR>GetCommModemStatus<BR>GetCommProperties<BR>GetCommState<BR>GetCommTimeouts<BR>GetDefaultCommConfig<BR>PurgeComm<BR>SetCommBreak<BR>SetCommConfig<BR>SetCommMask<BR>SetCommState<BR>SetCommTimeouts<BR>SetDefaultCommConfig<BR>SetupComm<BR>TransmitCommChar<BR>WaitCommEvent</P>
<P>我刚看到这些 API 的时候,都不知道如何使用它们,但并不是所有这些 API 都是必须用的,比如说你要检测当前串口的设置可以只用
SetCommState 而不用 GetCommProperties 和
GetCommConfig,虽然它们返回的信息可能更多。同样,如果有些值你想用缺省的,比如缓冲区的大小和超时的时间等等,那么
SetupComm 和 BuildCommDCBAndTimeouts、SetCommTimeouts
也可以不用,TransmitCommChar 是马上在发送序列中优先插入发送一个字符用的,平时也很少用到,下面讲的是必须用到的 API
和使用步骤:</P>
<OL>
<LI><FONT color=#ff6600>建立 Event -- 用
CreateEvent</FONT><BR><BR>invoke
CreateEvent,NULL,TRUE,FALSE,NULL<BR>用异步方式操作串口必须要定义 OVERLAPPED
结构,其中的 hEvent 必须自己建立,你要定义两个 OVERLAPPED 结构,一个用于读一个用于写,当然也必须建立两个
Event,把它们放入 OVERLAPPED.hEvent<BR><BR>
<LI><FONT color=#ff6600>打开串口 -- 用 CreateFile</FONT><BR><BR>invoke
CreateFile,addr szPortName,GENERIC_READ or
GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL<BR>注意用异步方式必须指定
FILE_FLAG_OVERLAPPED,而文件方式必须 OPEN_EXISTING,读写必须是 GENERIC_READ or
GENERIC_WRITE<BR><BR>
<LI><FONT color=#ff6600>设置串口参数 -- 用
SetCommState</FONT><BR><BR>invoke SetCommState,hCom,addr
dcbx<BR>hCom 是前面打开成功后返回的句柄,dcbx 是数据结构
DCB,里面包括了通讯的具体参数,至于这个参数的建立,你可以自己填写,也可以用前面提到的 BuildCommDCB 或
CommConfigDialog 填写<BR><BR>
<LI><FONT
color=#ff6600>建立读数据的线程</FONT><BR><BR>到这里,就可以开始读数据了,一般我们是在主线程中写数据,因为写是我们可以控制的,而读的时候我们不知道数据什么时候会到,所以要建立一个线程专门用来读数据,在这个线程中,我们循环地用
ReadFile 读串口,同时用 WaitCommEvent 检测线路状态。<BR><BR>
<LI>如果要<FONT color=#ff6600>检测通讯状态</FONT>,如 CTS 信号,RingIn 等等 -- 用
SetCommMask、WaitCommEvent、ClearCommError、GetCommModemStatus<BR><BR>invoke
SetCommMask,hCom,EV_BREAK or EV_CTS or EV_DSR or EV_ERR or EV_RING
or EV_RLSD or EV_RXCHAR or EV_RXFLAG or EV_TXEMPTY<BR>SetCommMask
指定 WaitCommEvent 要等待的事件名称,具体的参数请查手册<BR><BR>invoke
WaitCommEvent,hCom,addr dwEvent,NULL<BR>WaitCommEvent 等待一直到
SetCommMask 指定事件之一发生<BR><BR>invoke ClearCommError,hCom,addr
dwError,addr stComStat<BR>在 WaitCommEvent 以后,要用 ClearCommError
清除事件的 Flag,以便进行下一轮 WaitCommEvent,同时这个 API
可以获得更详细的事件信息<BR><BR>invoke GetCommModemStatus,hCom,addr
dwModemStatus<BR>同样,GetCommModemStatus 是用来获得串口线路状态的,如 CTS、RING
等等,当 WaitCommEvent 返回时,只是指出了如 CTS 等等状态有变化,但具体是变成 On 还是 Off 了还要靠这个
API 去取得更详细的信息<BR><BR>
<LI><FONT color=#ff6600>读数据 -- 用 ReadFile</FONT><BR><BR>invoke
ReadFile,hCom,addr szBuffer,sizeof szBuffer,addr dwBytesRead,addr
stReadState<BR>最后一个参数是开头定义的 OVERLAPPED 结构的地址,指定了它就表示是用异步方式的读方式,这个
API 会马上返回,接下去要用<BR><BR>invoke GetOverlappedResult,hCom,addr
stReadState,addr dwBytesRead,FALSE<BR>将其余的数据读完<BR><BR>
<LI><FONT color=#ff6600>结束时关闭端口 -- 停止 WaitCommEvent 的等待以及关闭端口
CloseHandle</FONT><BR><BR>平时程序会停留在 WaitCommEvent
的等待中,当要终止线程的时候,必须是程序从 WaitCommEvent 中退出来,这时候要用<BR><BR>按照 Win32
手册上的说明,参数为 NULL 的 SetCommMask 会使另一个线程中的 WaitCommEvent 马上返回,然后就是用
CloseHandle 关闭端口<BR>invoke CloseHandle,hCom </LI></OL>
<P><FONT color=#ff0000>5 Win32 通讯 API Bug 之二--- SetCommMask 和
WaitCommEvent</FONT><BR><BR>严格的说这不应该是
Bug,而是偶然的情况,我发现有些时候我的读线程无法结束,跟踪发现是停在了 WaitCommEvent 上,这说明有时候 invoke
SetCommMask,hCom,NULL 并不能使 WaitCommEvent 退出,我最后使用的办法是: 在 SetCommMask
以后再执行 invoke SetEvent,stReadState.hEvent,把读的 OVERLAPPED 结构中的 Event
置位,让 WaitCommEvent 认为有 Event 发生,它就会马上返回,也许这并不是普遍的情况,但如果你的程序也是停在了
WaitCommEvent 的地方,不妨一试。</P>
<P><FONT color=#ff0000>6 如何编写读线程中的循环</FONT><BR></P>
<P>按照<A
href="http://asm001.home.chinaren.com/program/tip-comm-prg.htm"
target=_blank>《Serial communications in Microsoft
Win32》</A>一文中的例程,读循环可以用:</P>
<P>#define READ_TIMEOUT 500 // milliseconds</P>
<P>DWORD dwRes;<BR>DWORD dwRead;<BR>BOOL fWaitingOnRead =
FALSE;<BR>OVERLAPPED osReader = {0};</P>
<P>// Create the overlapped event. Must be closed before
exiting<BR>// to avoid a handle leak.<BR>osReader.hEvent =
CreateEvent(NULL, TRUE, FALSE, NULL);</P>
<P>if (osReader.hEvent == NULL)<BR>// Error creating overlapped
event; abort.</P>
<P>if (!fWaitingOnRead) {<BR>// Issue read operation.<BR>if
(!ReadFile(hComm, lpBuf, READ_BUF_SIZE, &dwRead, &osReader))
{<BR>if (GetLastError() != ERROR_IO_PENDING) // read not
delayed?<BR>// Error in communications; report
it.<BR>else<BR>fWaitingOnRead = TRUE;<BR>}<BR>else {<BR>// read
completed immediately<BR>HandleASuccessfulRead(lpBuf,
dwRead);<BR>}<BR>}</P>
<P> </P>
<P>if (fWaitingOnRead) {<BR>dwRes =
WaitForSingleObject(osReader.hEvent,
READ_TIMEOUT);<BR>switch(dwRes)<BR>{<BR>// Read completed.<BR>case
WAIT_OBJECT_0:<BR>if (!GetOverlappedResult(hComm, &osReader,
&dwRead, FALSE))<BR>// Error in communications; report
it.<BR>else<BR>// Read completed
successfully.<BR>HandleASuccessfulRead(lpBuf, dwRead);</P>
<P>// Reset flag so that another opertion can be
issued.<BR>fWaitingOnRead = FALSE;<BR>break;</P>
<P>case WAIT_TIMEOUT:<BR>// Operation isn't complete yet.
fWaitingOnRead flag isn't<BR>// changed since I'll loop back around,
and I don't want<BR>// to issue another read until the first one
finishes.<BR>//<BR>// This is a good time to do some background
work.<BR>break;</P>
<P>default:<BR>// Error in the WaitForSingleObject; abort.<BR>//
This indicates a problem with the OVERLAPPED structure's<BR>// event
handle.<BR>break;<BR>}<BR>}</P>
<P> </P>
<P>这一段程序在 98 下正常,但非常不幸的是在 Win2000 下,ReadFile 总是返回读正确,并不返回
ERROR_IO_PENDING,使下面的 WaitForSingleObject 的循环形同虚设,要命的是,ReadFile
返回读正确却每次只读一个字节,结果程序工作得很奇怪,即使缓冲区中有很多的字符,程序也每次只能读一个字符,要等到发送字符或做其他的操作使线路状态改变了,才能读下一个字符,我不知道这个奇怪的现象是如何发生的,反正我解决的办法是在
ReadFile 前加 WaitCommEvent,真正等到 EV_RXCHAR 以后才去
ReadFile,到最后,我用的循环是这样的,虽然没有一篇文章中的例子是这样的,但它却同时在 windows9x 和
windows2000 下工作得很好:</P>
<P>.while dwFlag &
IF_CONNECT<BR>;********************************************************************<BR>;
检测其它的通信事件<BR>; 如果检测到且定义了 lpProcessEvent 则调用
lpProcessEvent<BR>;********************************************************************<BR>invoke
WaitCommEvent,hCom,addr @dwEvent,NULL ;addr stReadState<BR>push
eax<BR>invoke ClearCommError,hCom,addr @dwError,addr
@stComStat<BR>pop eax<BR>.if eax == 0<BR>invoke GetLastError<BR>.if
eax == ERROR_IO_PENDING<BR>or
dwFlag,IF_WAITING<BR>.endif<BR>.else<BR>;这里是线路状态的处理<BR>.endif<BR>;********************************************************************<BR>;
如果没有在等待异步读的过程中,则读端口<BR>;********************************************************************<BR>.if
! (dwFlag & IF_WAITING)<BR>mov @dwBytesRead,0<BR>invoke
ReadFile,hCom,addr @szBuffer,sizeof @szBuffer,\<BR>addr
@dwBytesRead,addr stReadState<BR>.if eax == FALSE<BR>or
dwFlag,IF_WAITING<BR>invoke GetLastError<BR>.if eax !=
ERROR_IO_PENDING<BR>;这里是错误处理<BR>.endif<BR>.else<BR>and dwFlag,not
IF_WAITING<BR>mov eax,@dwBytesRead<BR>.if eax !=
0<BR>;这里是接收到的数据处理<BR>.endif<BR>.endif<BR>.endif<BR>;********************************************************************<BR>;
如果在异步读端口中,则等待一段时间<BR>;********************************************************************<BR>.if
dwFlag & IF_WAITING<BR>invoke
WaitForSingleObject,stReadState.hEvent,200<BR>.if eax ==
WAIT_OBJECT_0<BR>and dwFlag,not IF_WAITING<BR>invoke
GetOverlappedResult,hCom,addr stReadState,\<BR>addr
@dwBytesRead,FALSE<BR>.if eax != 0<BR>mov eax,@dwBytesRead<BR>.if
eax != 0<BR>;这里是接收到的数据处理<BR>.endif<BR>.else<BR>;这里是错误处理<BR>invoke
ClearCommError,hCom,addr @dwError,addr
@stComStat<BR>.endif<BR>.else<BR>;这里是错误处理<BR>.endif<BR>.endif<BR>.endw<BR><BR><FONT
color=#ff0000>7 流控制的问题</FONT></P>
<P>在流控制方式为“无”和“软件控制”的情况下,基本上没有什么问题,但在“硬件控制”下,win32 手册中说明
RTS_CONTROL_HANDSHAKE 控制方式的含义是:</P>
<P>Enables RTS handshaking. The driver raises the RTS line when the
"type-ahead" (input) buffer is less than one-half full and lowers
the RTS line when the buffer is more than three-quarters full. If
handshaking is enabled, it is an error for the application to adjust
the line by using the EscapeCommFunction function.</P>
<P>也就是说,当缓冲区快满的时候 RTS 会自动 OFF 通知对方暂停发送,当缓冲区重新空出来的时候, RTS 会自动
ON,但我发现当 RTS 变 OFF 以后即使你已经清空了缓冲区, RTS 也不会自动的
ON,造成对方停在那里不发送了,所以,如果要用硬件流控制的话,还要在接收后最好加上检测缓冲区大小的判断,具体是使用
ClearCommError 后返回的 COMSTAT.cbInQue,当缓冲区已经空出来的时候,要使用 invoke
EscapeCommFunction,hCom,SETRTS 重新将 RTS 设置为
ON。</P></TD></TR></TBODY></TABLE>
<TABLE border=0 cellPadding=1 cellSpacing=1 width=550>
<TBODY>
<TR>
<TD>
<DIV align=center>
<HR align=center color=#99cccc SIZE=2 width=500>
</DIV></TD></TR>
<TR>
<TD vAlign=top width=424>
<P align=center><IMG border=0 height=74 src="实战串行通讯.files/coffe.gif"
width=57> </P></TD></TR>
<TR>
<TD>
<DIV align=center>
<HR align=center color=#99cccc SIZE=2 width=500>
</DIV></TD></TR>
<TR>
<TD vAlign=top width=424></TD></TR></TBODY></TABLE> <BR></TD>
<TD align=right background=实战串行通讯.files/zhe.gif height=443 rowSpan=2
vAlign=top width=10> </TD>
<TD align=middle bgColor=#cbe4e4 vAlign=top width=150> </TD></TR>
<TR>
<TD align=middle bgColor=#cbe4e4 height=18 vAlign=top
width=150> </TD></TR></TBODY></TABLE>
<TABLE align=center border=0 cellPadding=0 cellSpacing=0 height=28 width=758>
<TBODY>
<TR>
<TD background=实战串行通讯.files/looker.gif height=16 vAlign=center>
<P
align=right> &nb</P></TR></TBODY></TABLE></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -