1、深入浅出 VC+串口编程之基本概念 2006-02-17 09:43 作者:宋宝华出处:天极开发责任编辑:方舟引言在 PC 机的主板上,有一种类型的接口可能为我们所忽视,那就是 RS-232C 串行接口,在微软的Windows 系统中称其为 COM。我们可以通过设备管理器来查看 COM 的硬件参数设置,如图 1。图 1 在 Windows 上查看 PC 串口设置迄今为止,几乎每一台 PC 都包含 COM。本质而言,COM 是 PC 为和外界通信所提供的一种串行数据传输的接口。作为一种物理通信的途径和设备,它和目前风靡的另一种串行接口USB 所提供的功能是一致的。不过 RS-232C 显然已经开
2、始被后起之秀 USB 赶超,因为 USB 的传输速率已经远远超过了RS-232C。尽管如此,RS-232C 仍然具有非常广泛的应用,在相对长的一段时间里,难以被 USB 等接口取代。RS-232C 接口(又称 EIA RS-232C),1970 年由美国电子工业协会(EIA )联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定,全名是“数据终端设备(DTE)和数据通讯设备(DCE )之间串行二进制数据交换接口技术标准“。本文将对这一接口进行硬件原理的介绍,随后我们将逐章学习 DOS 平台的串口编程,及 Windows平台下基于 API、控件和第三方类的串口编程,最后本文将给出一个综合实例
3、。在本文的连载过程中,您可以通过如下方式联系作者(热忱欢迎读者朋友对本文的内容提出质疑或给出修改意见):作者 email:(可以来信提问,笔者将力求予以回信解答,并摘取其中的典型问题,在本系列文章最后一次连载的读者反馈中予以阐述);硬件原理众所周知,CPU 与存储芯片和 I/O 芯片的通信是并行的(并行传输的最大位数依赖于 CPU 的字长、数据总线的宽度),一种叫做 UART(通用异步收发器,Universal Asynchronous Receiver/Transmitter)的芯片提供了并行数据传输和 RS-232C 串行数据传输方式的转换。这样的设备通常有如图 2 所示的管脚分布,当其向
4、外传输数据时,CPU 并行的将数据写入这类芯片的寄存器, UART再将寄存器中的数据一位一位地移动并向外传输;当外界向其传输数据时,UART 一位一位地接收数据,并将其移位组合为并行数据,CPU 再并行地读取这些数据。实际上,由于 UART 芯片一般以 TTL/CMOS电平工作,在 UART 连接接口之前,还要经过一个 TTL/CMOS 和 RS-232C 电平的转换。RS-232C 规定了其标准的电气特性,逻辑 1 对应的电压必须在-5-15V 之间;逻辑 0 对应的的电压必须在+5+15V 之间。图 2 UART 并/串转换一个常见的 TTL/CMOS 和 RS-232C 电平转换芯片如图
5、 3。图 3 常见的 TTL/CMOS 和 RS-232C 电平转换芯片RS-232C 通常以两类接插件与外界相连,分别称为 DB9 和 DB25,如图 4 所示。图 4 DB9 和 DB25而接插件中各个针的定义则如表 1:表 1 DB9 和 DB25 引脚定义DB9 DB25针号 功能说明 缩写 针号 功能说明 缩写1 数据载波检测 DCD 8 数据载波检测 DCD2 接收数据 RXD 3 接收数据 RXD3 发送数据 TXD 2 发送数据 TXD4 数据终端准备 DTR 20 数据终端准备 DTR5 信号地 GND 7 信号地 GND6 数据设备准备好 DSR 6 数据准备好 DSR7
6、请求发送 RTS 4 请求发送 RTS8 清除发送 CTS 5 清除发送 CTS9 振铃指示 DELL 22 振铃指示 DELLRS-232C 定义为数据通信设备(DCE)和数据终端设备(DTE)之间的互连,实现上,到现在为止,究竟一个设备属于 DCE 还是属于 DTE 已经没有明显的界限,PC 即可作为 DCE,又可作为 DTE。两串口互连,连接方法主要有二:一种方法是,数据的发送和接收由软件控制,不进行硬件握手,其连接方法如图 5(最常用 DB9 连接示意)和表 2( DB9、DB25 三线连接表),真正需要互相连接的是 RXD、TXD 和 GND;图 5 无硬件握手时两串口连接表 2 D
7、B9、DB25 三线连接9 针9 针 5 针25 针 2 9 针 25 针2 3 3 2 2 23 2 2 3 3 3 5 5 7 7 5 7 软件握手又称为 XON/XOFF,通常以 CTRL-S(0x13)和 CTRL-Q(0x11 )两个字符来实现流控制。前者用于请求对方暂停发送,后者用于清除暂停传送的请求,继续发送数据。另一种方法是,数据的发送和接收由硬件控制,进行硬件握手,其连接方法如图 6(最常用 DB9 连接示意),需要连接的信号除 RXD、TXD 和 GND 外,还包括 DTR、DSR、RTS 和 CTS。硬件握手依赖于 RTS 和 CTS 信号,当发送设备欲发送数据时,将 R
8、TS 信号置为有效表示请求发送,接收设备准备好后,置 CTS 信号有效,接着发送设备通过信号线 TXD 开始发送串行数据。这里我们联想开来,RTS/CTS 模式在许多领域里都出现过。回忆一下 IEEE 802.11 无线局域网协议标准,在其 MAC 协议中就使用了 RTS/CTS,RTS/CTS 抽象开来就是一种请求/应答。笔者曾经在拙作中多次以实例论证计算机领域里许多知识的相通性,这又是一个明证。图 6 有硬件握手时两串口连接实际上,目前我们经常使用的是方法一,即只连接 RXD、TXD 和 GND,简单灵活。另外,串口之间互连还有诸多途径,如图 7 所示。图 7 其它互连方式Win32 串
9、口 编 程作 者 : 韩 耀 旭下 载 源 代 码在 工 业 控 制 中 , 工 控 机 ( 一 般 都 基 于 Windows 平 台 ) 经 常 需 要 与 智 能 仪 表 通过 串 口 进 行 通 信 。 串 口 通 信 方 便 易 行 , 应 用 广 泛 。一 般 情 况 下 , 工 控 机 和 各 智 能 仪 表 通 过 RS485 总 线 进 行 通 信 。 RS485 的 通 信 方式 是 半 双 工 的 , 只 能 由 作 为 主 节 点 的 工 控 PC 机 依 次 轮 询 网 络 上 的 各 智 能 控 制 单元 子 节 点 。 每 次 通 信 都 是 由 PC 机 通 过
10、 串 口 向 智 能 控 制 单 元 发 布 命 令 , 智 能 控 制 单元 在 接 收 到 正 确 的 命 令 后 作 出 应 答 。在 Win32 下 , 可 以 使 用 两 种 编 程 方 式 实 现 串 口 通 信 , 其 一 是 使 用 ActiveX控 件 , 这 种 方 法 程 序 简 单 , 但 欠 灵 活 。 其 二 是 调 用 Windows 的 API 函 数 , 这 种方 法 可 以 清 楚 地 掌 握 串 口 通 信 的 机 制 , 并 且 自 由 灵 活 。 本 文 我 们 只 介 绍 API 串口 通 信 部 分 。串 口 的 操 作 可 以 有 两 种 操 作
11、 方 式 : 同 步 操 作 方 式 和 重 叠 操 作 方 式 ( 又 称 为 异 步操 作 方 式 ) 。 同 步 操 作 时 , API 函 数 会 阻 塞 直 到 操 作 完 成 以 后 才 能 返 回 ( 在 多 线 程方 式 中 , 虽 然 不 会 阻 塞 主 线 程 , 但 是 仍 然 会 阻 塞 监 听 线 程 ) ; 而 重 叠 操 作 方 式 , API函 数 会 立 即 返 回 , 操 作 在 后 台 进 行 , 避 免 线 程 的 阻 塞 。无 论 那 种 操 作 方 式 , 一 般 都 通 过 四 个 步 骤 来 完 成 :( 1) 打 开 串 口( 2) 配 置 串
12、 口( 3) 读 写 串 口( 4) 关 闭 串 口( 1) 打 开 串 口 Win32 系 统 把 文 件 的 概 念 进 行 了 扩 展 。 无 论 是 文 件 、 通 信 设 备 、 命 名 管 道 、 邮件 槽 、 磁 盘 、 还 是 控 制 台 , 都 是 用 API 函 数 CreateFile 来 打 开 或 创 建 的 。 该 函 数的 原 型 为 : HANDLE CreateFile( LPCTSTR lpFileName,DWORD dwDesiredAccess,DWORD dwShareMode,LPSECURITY_ATTRIBUTES lpSecurityAttr
13、ibutes,DWORD dwCreationDistribution,DWORD dwFlagsAndAttributes,HANDLE hTemplateFile); lpFileName: 将 要 打 开 的 串 口 逻 辑 名 , 如 “COM1”; dwDesiredAccess: 指 定 串 口 访 问 的 类 型 , 可 以 是 读 取 、 写 入 或 二 者 并 列 ; dwShareMode: 指 定 共 享 属 性 , 由 于 串 口 不 能 共 享 , 该 参 数 必 须 置 为0; lpSecurityAttributes: 引 用 安 全 性 属 性 结 构 , 缺
14、省 值 为 NULL; dwCreationDistribution: 创 建 标 志 , 对 串 口 操 作 该 参 数 必 须 置 为OPEN_EXISTING; dwFlagsAndAttributes: 属 性 描 述 , 用 于 指 定 该 串 口 是 否 进 行 异 步 操 作 , 该值 为 FILE_FLAG_OVERLAPPED, 表 示 使 用 异 步 的 I/O; 该 值 为 0, 表 示 同步 I/O 操 作 ; hTemplateFile: 对 串 口 而 言 该 参 数 必 须 置 为 NULL; 同 步 I/O 方 式 打 开 串 口 的 示 例 代 码 : HAN
15、DLE 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 =Cr
16、eateFile(“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) 、 配 置 串 口 在 打 开 通 讯 设 备 句 柄 后 , 常 常 需 要 对 串
17、口 进 行 一 些 初 始 化 配 置 工 作 。 这 需 要 通过 一 个 DCB 结 构 来 进 行 。 DCB 结 构 包 含 了 诸 如 波 特 率 、 数 据 位 数 、 奇 偶 校 验 和 停止 位 数 等 信 息 。 在 查 询 或 配 置 串 口 的 属 性 时 , 都 要 用 DCB 结 构 来 作 为 缓 冲 区 。一 般 用 CreateFile 打 开 串 口 后 , 可 以 调 用 GetCommState 函 数 来 获 取 串 口 的初 始 配 置 。 要 修 改 串 口 的 配 置 , 应 该 先 修 改 DCB 结 构 , 然 后 再 调 用SetCommSt
18、ate 函 数 设 置 串 口 。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
19、_57600, CBR_115200, CBR_128000, CBR_256000, CBR_14400DWORD fParity; / 指 定 奇 偶 校 验 使 能 。 若 此 成 员 为 1, 允 许 奇 偶 校 验检 查 BYTE ByteSize; / 通 信 字 节 位 数 , 48BYTE Parity; /指 定 奇 偶 校 验 方 法 。 此 成 员 可 以 有 下 列 值 :EVENPARITY 偶 校 验 NOPARITY 无 校 验MARKPARITY 标 记 校 验 ODDPARITY 奇 校 验BYTE StopBits; /指 定 停 止 位 的 位 数 。 此
20、 成 员 可 以 有 下 列 值 :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 CB
21、R_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 函 数
22、可 以 获 得 COM 口 的 设 备 控 制 块 , 从 而 获 得 相 关 参 数 : BOOL GetCommState(HANDLE hFile, /标 识 通 讯 端 口 的 句 柄LPDCB lpDCB /指 向 一 个 设 备 控 制 块 ( DCB 结 构 ) 的 指 针);SetCommState 函 数 设 置 COM 口 的 设 备 控 制 块 :BOOL SetCommState(HANDLE hFile, LPDCB lpDCB );除 了 在 BCD 中 的 设 置 外 , 程 序 一 般 还 需 要 设 置 I/O 缓 冲 区 的 大 小 和 超 时 。Windo
23、ws 用 I/O 缓 冲 区 来 暂 存 串 口 输 入 和 输 出 的 数 据 。 如 果 通 信 的 速 率 较 高 , 则 应该 设 置 较 大 的 缓 冲 区 。 调 用 SetupComm 函 数 可 以 设 置 串 行 口 的 输 入 和 输 出 缓 冲区 的 大 小 。 BOOL SetupComm(HANDLE hFile, / 通 信 设 备 的 句 柄 DWORD dwInQueue, / 输 入 缓 冲 区 的 大 小 ( 字 节 数 ) DWORD dwOutQueue / 输 出 缓 冲 区 的 大 小 ( 字 节 数 ));在 用 ReadFile 和 WriteF
24、ile 读 写 串 行 口 时 , 需 要 考 虑 超 时 问 题 。 超 时 的 作 用 是在 指 定 的 时 间 内 没 有 读 入 或 发 送 指 定 数 量 的 字 符 , ReadFile 或 WriteFile 的 操 作仍 然 会 结 束 。要 查 询 当 前 的 超 时 设 置 应 调 用 GetCommTimeouts 函 数 , 该 函 数 会 填 充 一 个COMMTIMEOUTS 结 构 。 调 用 SetCommTimeouts 可 以 用 某 一 个COMMTIMEOUTS 结 构 的 内 容 来 设 置 超 时 。读 写 串 口 的 超 时 有 两 种 : 间 隔
25、 超 时 和 总 超 时 。 间 隔 超 时 是 指 在 接 收 时 两 个 字 符之 间 的 最 大 时 延 。 总 超 时 是 指 读 写 操 作 总 共 花 费 的 最 大 时 间 。 写 操 作 只 支 持 总 超 时 ,而 读 操 作 两 种 超 时 均 支 持 。 用 COMMTIMEOUTS 结 构 可 以 规 定 读 写 操 作 的 超 时 。COMMTIMEOUTS 结 构 的 定 义 为 : typedef struct _COMMTIMEOUTS DWORD ReadIntervalTimeout; /读 间 隔 超 时DWORD ReadTotalTimeoutMult
26、iplier; /读 时 间 系 数DWORD ReadTotalTimeoutConstant; /读 时 间 常 量DWORD WriteTotalTimeoutMultiplier; / 写 时 间 系 数DWORD WriteTotalTimeoutConstant; /写 时 间 常 量 COMMTIMEOUTS,*LPCOMMTIMEOUTS;COMMTIMEOUTS 结 构 的 成 员 都 以 毫 秒 为 单 位 。 总 超 时 的 计 算 公 式 是 :总 超 时 时 间 系 数 要 求 读 /写 的 字 符 数 时 间 常 量 例 如 , 要 读 入 10 个 字 符 , 那
27、 么 读 操 作 的 总 超 时 的 计 算 公 式 为 :读 总 超 时 ReadTotalTimeoutMultiplier10 ReadTotalTimeoutConstant 可 以 看 出 : 间 隔 超 时 和 总 超 时 的 设 置 是 不 相 关 的 , 这 可 以 方 便 通 信 程 序 灵 活 地 设 置各 种 超 时 。 如 果 所 有 写 超 时 参 数 均 为 0, 那 么 就 不 使 用 写 超 时 。 如 果 ReadIntervalTimeout为 0, 那 么 就 不 使 用 读 间 隔 超 时 。 如 果 ReadTotalTimeoutMultiplier
28、 和 ReadTotalTimeoutConstant 都 为 0, 则 不 使 用 读 总 超 时 。 如 果 读 间 隔 超 时 被 设 置成 MAXDWORD 并 且 读 时 间 系 数 和 读 时 间 常 量 都 为 0, 那 么 在 读 一 次 输 入 缓 冲 区的 内 容 后 读 操 作 就 立 即 返 回 , 而 不 管 是 否 读 入 了 要 求 的 字 符 。在 用 重 叠 方 式 读 写 串 口 时 , 虽 然 ReadFile 和 WriteFile 在 完 成 操 作 以 前 就 可能 返 回 , 但 超 时 仍 然 是 起 作 用 的 。 在 这 种 情 况 下 , 超 时 规 定 的 是 操 作 的 完 成 时 间 ,