news 2026/4/18 9:36:07

汇编语言全接触-70.实战串行通讯

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
汇编语言全接触-70.实战串行通讯

很长时间没有写编程文章了,最近为了编一个串行通讯的终端程序忙了近一个月,发现了在 Win32 的通讯 API 中有不少的问题,还有许多在其他串行通讯文章中没有谈到的问题,在这里准备集中谈一下,大家请下载我这一个月的“成果”:迷你终端测试一下,看看还有没有什么问题。有关串行通讯方面的基础文章,可以参考《Serial communications in Microsoft Win32》和《串行通讯编程点滴》。本文不是全面的讲述如何编写串行通讯程序,而是讨论一些实际遇到的问题。

1 选择通讯方式 -- 同步还是非同步

正如在《Serial communications in Microsoft Win32》等文章中提到的,同步(NonOverLapped)方式是比较简单的一种方式,编写起来代码的长度要明显少于异步(OverLapped)方式,我开始用同步方式编写了整个子程序,在 Windows98 下工作正常,但后来在 Windows2000下测试,发现接收正常,但一发送数据,程序就会停在那里,原因应该在于同步方式下如果有一个通讯 Api 在操作中,另一个会阻塞直到上一个操作完成,所以当读数据的线程停留在 WaitCommEvent 的时候,WriteFile 就停在那里。我又测试了我手上所有有关串行通讯的例子程序,发现所有使用同步方式的程序在 Windows 2000 下全部工作不正常,对这个问题我一直找不到解决的办法,后来在 Iczelion 站点上发现一篇文章提到 NT 下对串行通讯的处理和 9x 有些不同,根本不要指望在 NT 或 Windows 2000 下用同步方式同时收发数据,我只好又用异步方式把整个通讯子程序重新写了一遍。

所以对于这个问题的建议是:如果程序只打算工作在 Win9x 下,为了简单起见,可以用同步方式写程序,如果程序打算在 NT 下也可以工作的话,就必须用异步方式写。

2 Win32 通讯 API Bug 之一 --- CommConfigDialog

CommConfigDialog 是弹出系统内置串口设置对话框的 API,我们在设备管理器中设置串口参数的对话框就是这个,使用这个 API 时不用先打开端口,它并不针对一个已打开的端口,而是仅仅是把 DCB 的内容填写到对话框中,当按了 OK 后把输入的结果存回到 DCB 数据结构中,至于什么时候把结果设置到串口上,那就是你自己要做的事情了。

CommCinfigDialog 的定义如下:

BOOL CommConfigDialog(

LPTSTR lpszName, // pointer to device name string

HWND hWnd, // handle to window

LPCOMMCONFIG lpCC // pointer to comm. configuration structure

);

但在使用中发现,对话框有时能出来,有时出不来,最后总结的经验是问题出在 COMMCONFIG 结构的 dwSize 字段上,COMMCONFIG 的定义如下:

typedef struct _COMM_CONFIG {

DWORD dwSize;

WORD wVersion;

WORD wReserved;

DCB dcb;

DWORD dwProviderSubType;

DWORD dwProviderOffset;

DWORD dwProviderSize;

WCHAR wcProviderData[1];

} COMMCONFIG, *LPCOMMCONFIG;

在参数中,wVersion 要填 100h,dwProviderSubType 要填 1,但 dwSize 就不能填 sizeof COMMCONFIG 了,我发现好象一定要把 dwSize 设置为比 sizeof COMMCONFIG 对话框才能出来,所以我用的代码中定义了一个足够大的缓冲区作为结构的地址:

_CommConfigDialog proc

local @stCC[256]:BYTE

pushad

invoke RtlZeroMemory,addr @stCC,sizeof @stCC

mov (COMMCONFIG ptr @stCC).dwSize,256

mov (COMMCONFIG ptr @stCC).wVersion,100h

mov (COMMCONFIG ptr @stCC).dwProviderSubType,1

invoke CommConfigDialog,addr [esi].szPortName,[esi].hWnd,addr @stCC

popad

ret

_CommConfigDialog endp

3 Win32 通讯 API Bug 之二--- BuildCommDCB

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 设置回去,我用的代码是:

_BuildCommDCB proc _lpszPara,_lpstDCB

pushad

mov esi,_lpstDCB

assume esi:ptr DCB

invoke RtlZeroMemory,esi,sizeof DCB

invoke BuildCommDCB,_lpszPara,esi

;********************************************************************

; 根据校验位补充设置 DCB 中的 DCB.fbits.fParity 字段

;********************************************************************

mov dword ptr [esi].fbits,0010b

cld

@@:

lodsb

or al,al

jz @F

cmp al,'='

jz _BCD_Check

cmp al,','

jnz @B

_BCD_Check:

lodsb

or al,al

jz @F

or al,20h

cmp al,'n'

jnz @B

;********************************************************************

; 扫描到 =n 或 ,n 则取消校验位

;********************************************************************

mov esi,_lpstDCB

and dword ptr [esi].fbits,not 0010b

@@:

popad

ret

_BuildCommDCB endp

4 Win32 通讯编程的一般流程

由于同步方式相对比较简单,在这里讲述的是异步方式的流程,在其他的很多文章里提到了 Windows 通讯 API 有二十多个,它们是:

BuildCommDCB

BuildCommDCBAndTimeouts

ClearCommBreak

ClearCommError

CommConfigDialog

EscapeCommFunction

GetCommConfig

GetCommMask

GetCommModemStatus

GetCommProperties

GetCommState

GetCommTimeouts

GetDefaultCommConfig

PurgeComm

SetCommBreak

SetCommConfig

SetCommMask

SetCommState

SetCommTimeouts

SetDefaultCommConfig

SetupComm

TransmitCommChar

WaitCommEvent

我刚看到这些 API 的时候,都不知道如何使用它们,但并不是所有这些 API 都是必须用的,比如说你要检测当前串口的设置可以只用 SetCommState 而不用 GetCommProperties 和 GetCommConfig,虽然它们返回的信息可能更多。同样,如果有些值你想用缺省的,比如缓冲区的大小和超时的时间等等,那么 SetupComm 和 BuildCommDCBAndTimeouts、SetCommTimeouts 也可以不用,TransmitCommChar 是马上在发送序列中优先插入发送一个字符用的,平时也很少用到,下面讲的是必须用到的 API 和使用步骤:

建立 Event -- 用 CreateEvent

invoke CreateEvent,NULL,TRUE,FALSE,NULL

用异步方式操作串口必须要定义 OVERLAPPED 结构,其中的 hEvent 必须自己建立,你要定义两个 OVERLAPPED 结构,一个用于读一个用于写,当然也必须建立两个 Event,把它们放入 OVERLAPPED.hEvent

打开串口 -- 用 CreateFile

invoke CreateFile,addr szPortName,GENERIC_READ or GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL

注意用异步方式必须指定 FILE_FLAG_OVERLAPPED,而文件方式必须 OPEN_EXISTING,读写必须是 GENERIC_READ or GENERIC_WRITE

设置串口参数 -- 用 SetCommState

invoke SetCommState,hCom,addr dcbx

hCom 是前面打开成功后返回的句柄,dcbx 是数据结构 DCB,里面包括了通讯的具体参数,至于这个参数的建立,你可以自己填写,也可以用前面提到的 BuildCommDCB 或 CommConfigDialog 填写

建立读数据的线程

到这里,就可以开始读数据了,一般我们是在主线程中写数据,因为写是我们可以控制的,而读的时候我们不知道数据什么时候会到,所以要建立一个线程专门用来读数据,在这个线程中,我们循环地用 ReadFile 读串口,同时用 WaitCommEvent 检测线路状态。

如果要检测通讯状态,如 CTS 信号,RingIn 等等 -- 用 SetCommMask、WaitCommEvent、ClearCommError、GetCommModemStatus

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

SetCommMask 指定 WaitCommEvent 要等待的事件名称,具体的参数请查手册

invoke WaitCommEvent,hCom,addr dwEvent,NULL

WaitCommEvent 等待一直到 SetCommMask 指定事件之一发生

invoke ClearCommError,hCom,addr dwError,addr stComStat

在 WaitCommEvent 以后,要用 ClearCommError 清除事件的 Flag,以便进行下一轮 WaitCommEvent,同时这个 API 可以获得更详细的事件信息

invoke GetCommModemStatus,hCom,addr dwModemStatus

同样,GetCommModemStatus 是用来获得串口线路状态的,如 CTS、RING 等等,当 WaitCommEvent 返回时,只是指出了如 CTS 等等状态有变化,但具体是变成 On 还是 Off 了还要靠这个 API 去取得更详细的信息

读数据 -- 用 ReadFile

invoke ReadFile,hCom,addr szBuffer,sizeof szBuffer,addr dwBytesRead,addr stReadState

最后一个参数是开头定义的 OVERLAPPED 结构的地址,指定了它就表示是用异步方式的读方式,这个 API 会马上返回,接下去要用

invoke GetOverlappedResult,hCom,addr stReadState,addr dwBytesRead,FALSE

将其余的数据读完

结束时关闭端口 -- 停止 WaitCommEvent 的等待以及关闭端口 CloseHandle

平时程序会停留在 WaitCommEvent 的等待中,当要终止线程的时候,必须是程序从 WaitCommEvent 中退出来,这时候要用

按照 Win32 手册上的说明,参数为 NULL 的 SetCommMask 会使另一个线程中的 WaitCommEvent 马上返回,然后就是用 CloseHandle 关闭端口

invoke CloseHandle,hCom

5 Win32 通讯 API Bug 之二--- SetCommMask 和 WaitCommEvent

严格的说这不应该是 Bug,而是偶然的情况,我发现有些时候我的读线程无法结束,跟踪发现是停在了 WaitCommEvent 上,这说明有时候 invoke SetCommMask,hCom,NULL 并不能使 WaitCommEvent 退出,我最后使用的办法是: 在 SetCommMask 以后再执行 invoke SetEvent,stReadState.hEvent,把读的 OVERLAPPED 结构中的 Event 置位,让 WaitCommEvent 认为有 Event 发生,它就会马上返回,也许这并不是普遍的情况,但如果你的程序也是停在了 WaitCommEvent 的地方,不妨一试。

6 如何编写读线程中的循环

按照《Serial communications in Microsoft Win32》一文中的例程,读循环可以用:

#define READ_TIMEOUT 500 // milliseconds

DWORD dwRes;

DWORD dwRead;

BOOL fWaitingOnRead = FALSE;

OVERLAPPED osReader = {0};

// Create the overlapped event. Must be closed before exiting

// to avoid a handle leak.

osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

if (osReader.hEvent == NULL)

// Error creating overlapped event; abort.

if (!fWaitingOnRead) {

// Issue read operation.

if (!ReadFile(hComm, lpBuf, READ_BUF_SIZE, &dwRead, &osReader)) {

if (GetLastError() != ERROR_IO_PENDING) // read not delayed?

// Error in communications; report it.

else

fWaitingOnRead = TRUE;

}

else {

// read completed immediately

HandleASuccessfulRead(lpBuf, dwRead);

}

}

if (fWaitingOnRead) {

dwRes = WaitForSingleObject(osReader.hEvent, READ_TIMEOUT);

switch(dwRes)

{

// Read completed.

case WAIT_OBJECT_0:

if (!GetOverlappedResult(hComm, &o

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 1:33:03

你不可不知的Dify access_token 隐患:这些错误正在影响线上服务

第一章:Dify access_token 异常 在使用 Dify 平台进行 API 集成时,access_token 异常是常见的身份验证问题之一。此类异常通常表现为请求返回 401 Unauthorized 或提示 token 无效、过期、格式错误等信息。正确识别并处理这些异常对保障系统稳定性和安全…

作者头像 李华
网站建设 2026/4/18 0:56:13

5分钟精通多语言排版:开源字体库Estedad终极实战指南

想要在项目中优雅地处理阿拉伯语、波斯语和拉丁语的混合排版吗?Estedad开源字体库为您提供了一站式解决方案。这款支持多语言的无衬线字体不仅覆盖从Thin(100)到Black(900)的9种标准字重,还提供可变字体版本,让您轻松应对复杂的国际化排版需求…

作者头像 李华
网站建设 2026/4/17 17:51:41

Dify描述生成异常如何快速恢复:一线工程师的实战排错流程

第一章:Dify描述生成异常的核心机制解析在使用 Dify 平台进行 AI 应用开发过程中,描述生成异常是开发者常遇到的问题之一。该异常通常表现为系统无法正确解析用户输入的自然语言指令,导致工作流中断或输出不符合预期。其核心机制涉及自然语言…

作者头像 李华
网站建设 2026/4/15 9:43:28

百度网盘高速下载工具:彻底解决限速问题的完整指南

百度网盘高速下载工具:彻底解决限速问题的完整指南 【免费下载链接】baiduwp-php A tool to get the download link of the Baidu netdisk / 一个获取百度网盘分享链接下载地址的工具 项目地址: https://gitcode.com/gh_mirrors/ba/baiduwp-php 还在为百度网…

作者头像 李华
网站建设 2026/4/18 8:34:30

GLM-4.6V-Flash-WEB能否解析发票或收据图像?

GLM-4.6V-Flash-WEB能否解析发票或收据图像? 在企业财务流程中,每天都有成千上万张发票和收据需要录入、核对与归档。传统的人工处理方式不仅效率低下,还容易因视觉疲劳或格式差异导致错误。尽管OCR技术早已被广泛应用于文档数字化&#xff0…

作者头像 李华
网站建设 2026/4/17 21:05:17

Nucleus Co-op:单机游戏分屏革命,打造极致本地多人体验

Nucleus Co-op:单机游戏分屏革命,打造极致本地多人体验 【免费下载链接】splitscreenme-nucleus Nucleus Co-op is an application that starts multiple instances of a game for split-screen multiplayer gaming! 项目地址: https://gitcode.com/gh…

作者头像 李华