串口通信编程有关资料
用Activex实现串口通信
WIN95 界 面 下 的VC++ 串 口 通 讯 程 序
利 用WINDOWS 定 时 器 实 现 串 口 通 信 控 制
VC++下串行通信的编程方法
串行通讯通用接口程序设计
VC在win95下编写用于串行通讯的程序
用Activex实现串口通信
为实现Windows的串口通信可用API实现,但十分不便。也可以用Active控件实现。
先在应用程序中插入Active控件Microsoft comm contronl(MSComm)。
SComm控件的属性
MSComm控件的属性
CommPort:设置串口号,类型 short :1-comm1 2-comm2.
Settings:设置串口通讯参数,类型 CString :B-波特率 P-奇偶性?
∟-无校验,E-偶校验,O-奇验) D-字节有效位数S-停止位。
PortOpen:设置或返回串口状态,类型 BOOL:TURE-打开 FALSE-关闭。
InputMode:设置从接收缓冲区读取数据的格式,类型 long: 0-Text 1-Bin.
Input:从接收缓冲区读取数据 类型 VARIANT。
InBufferCount:接收缓冲区中的字节数,类型:short.
InBufferSize:接收缓冲区的大小,类型:short.
Output:向发送缓冲区写入数据,类型:VARIANT.
OutBufferCount:发送缓冲区中的字节数,类型:short.
OutBufferSize:发送缓冲区的大小,类型:short.
InputLen:设置或返回Input读出的字节数,类型:short.
CommEvent:串口事件,类型:short.
程序示例
串口初始化
if(!m_comm.GetPortOpen())
m_comm.SetPortOpen(TURE); /*打开串口
m_comm.SetSettings("4800,n,8,1"); /*串口参数设置
m_comm.SetInputMode(0); /*设置TEXT缓冲区输入方式
m_comm.SetRthresHold(1); /*每接收一个字符则激发OnComm()事件
接收数据
m_comm.SetInputLen(1); /*每次读取一个字符
VARINAT V1=m_comm.GetInput(); /*读入字符
m_V1=V1.bstrval;
发送字符
m_comm.SetOutput(COlevariant("Hello");/*发送"Hello"
WIN95 界 面 下 的VC++ 串 口 通 讯 程 序
南 京 石 油 物 探 研 究 所
吴 凤 华
---- Microsoft Visual C++ 以 其 界 面 的 开 放 性 著 称, 因 其 简 单 易 学、32 位
面 向 对 象 的 程 序 设 计 及Activate X 的 灵 活 性 而 受 广 大 计 算 机 开
发 者 的 青 睐, 广 泛 应 用 于 各 个 领 域。 怎 样 利 用VC++ 进 行 串 口 的
通 讯 编 程, 这 是 一 种 基 本 功。 特 别 是 在 实 时 监 控、 大 小 型 设
备 的 整 个 生 产 过 程 控 制 等 方 面,VC++ 的 串 口 编 程 显 得 尤 为 重
要。 我 曾 参 与 编 写 了 无 线 电 非 法 频 段 占 用 的 实 时 监 控 系 统、
混 凝 土 搅 拌 自 动 化 生 产 控 制 系 统。 下 面 就 自 己 的 一 点 经 验
体 会 与 读 者 分 享。
---- 一 般 情 况 下 的 串 口 通 讯 无 非 采 用 两 种 方 法: 一 是 利 用Windows 的 通
讯API 函 数, 二 是 利 用VC++( 或 其 它 语 言) 的 标 准 通 讯 函 数_inp、_outp 来 实 现。
---- Windows 的API 函 数 虽 然 使 用 面 很 广, 但 由 于 比 较 复 杂 而 有 一 定 的 难
度, 故 简 单 述 之, 其 编 程 过 程 分 为 初 始 化 串 口、 配 置 串 口、 超 时 控 制、
数 据 传 输、 关 闭 串 口 等。
---- VC++ 串 口 通 讯
---- 一、 初 始 化 串 行 通 讯 口
---- 通 过 计 算 机 串 口 进 行 通 讯 之 前, 必 须 根 据 监 控 设 备 的 有 关 通 讯 参
数, 约 定 双 方 的 通 讯 方 式, 包 括 波 特 率 的 设 置、 奇 偶 位 校 验 及 停 止 位 的
设 立。 确 定 数 据 传 输 帧 格 式, 确 定UART 操 作 方 式。 逐 个 对 线 路 控 制 寄 存
器、 波 特 率 因 子 寄 存 器 和M0DEM 寄 存 器 写 入 操 作。
---- 先 确 定 计 算 机 的 通 讯 口 地 址 是3F8 还 是2F8( 根 据 用 户 的 不 同 要 求, 可
加 插 卡 而 有3E8、2E8, 甚 至 使 用 多 用 户 卡 而 有3220、3228、4220、4228、5220、5228)
, 例 中 以2F8 做 为 端 口 地 址。 利 用_outp(PORT,0x0c) 将 波 特 率 设 置 为9600,(_outp
(PORT,0x30) 将 波 特 率 设 置 为2400,_outp(PORT,0x18) 将 波 特 率 设 置 为4800)。
---- 数 据 传 输 格 式 则 由 地 址PORT+3 确 定, 如 选 用 七 位 一 停 止 位 偶 校 验, 则
采 用outp(PORT+3,0x3a) 及outp(PORT+3 ,0x1a) 两 个 语 句; 如 选 用 八 位 一 停 止 位 无
奇 偶 校 验, 则 采 用outp(PORT+3,0x3a) 及outp(PORT+3 ,0x03) 两 个 语 句; 至 于 为 什 么
, 可 参 考 有 关 异 步 通 讯 数 据 格 式 方 面 的 书。
---- 二、 查 询 发 送 流 程
---- 只 需CPU 检 测 到UART 发 送 器 保 持 寄 存 器 为 空, 即 向UART 输 出 一 个 字 符。 发
送 方 首 先 输 出RTS 和DTR 有 效, 检 测MODEM 寄 存 器, 只 有 收 到DEC 输 入 的CTS、DSR 有
效,CPU 才 向UART 输 出 一 发 送 字 符。
---- 三、 查 询 接 收 流 程
---- 只 需CPU 检 测 到UART 接 收 器 数 据 准 备 就 绪, 即 可 从 接 收 器 数 据 寄 存 器 中
读 取 一 字 符。 接 收 方 首 先 输 出 数 据 终 端 就 绪 有 效(DTR=1), 然 后 检 测MODEM 状
态 寄 存 器, 只 有DSR=1,CPU 才 接 收 一 字 符。
---- 程 序 例
---- 为 了 编 程 的 方 便, 我 把 实 时 监 控 过 程 中 的 串 口 初 始 化、 串 口 接 收、 串
口 发 送 等 以 子 程 序 的 形 式 呈 现。
确 定 通 讯 口 地 址:int PORT=0x2F8;
接 收 字 符 子 程 序:
char near readbyte(void)
{
int time_limit;
char sta;
time_limit=5000;
while ((sta=inp(PORT+5) & 0x01) !=1)
{ time_limit--;
if (time_limit==0) {
/*printf("s=%4xm ",sta);*/return 0;}
}
return inp(PORT);
printf("%s",inp(PORT));
}
发 送 字 符 子 程 序:
void sendbyte(unsigned char sdata)
{
long int time_limit;
time_limit=50000;
while ((inp(PORT+5) & 0xf0) != 0x60)
{ time_limit--;
if (time_limit==0) break;
}
outp(PORT,sdata);
}
串 口 初 始 化 子 程 序:
void init_com(PORT)
{
char i;
outp(PORT+3,0x80);
outp(PORT ,0x0C); /* baud rate 9600 */
outp(PORT+1,0);
/*8bit 1stop no even */
outp(PORT+3 ,0x3a);
outp(PORT+3 ,0x03);
i=inp(PORT+5) & 0xfe;
outp(PORT+5,i);
}
字 符 串 发 送 子 程 序:
void sendstrn(char *mess)
{
for (; *mess; mess++)
sendbyte(*mess);
sendbyte(0X0D);
/*sendbyte(0X0A); */
}
简 单 的 主 程 序:
init_com(ox2f8);
sendstrn(PORT,"FUNC");
j=0;
line=0;
do {
for(i=0;i<25;i++) ttemp[i]="\0" ; if(readbyte(PORT)="='M')" { i="0;" do { ttemp[i]="readbyte(PORT);" i++; }
while((ttemp[i-2]!="13)&&(ttemp[i-1]!=10));" line++; dcqd.TextOut(450,line*20+180,ttemp);} j++;}
while((j< 1000)&&(line< 5)); Wed, 16 Sep 1998 "acejet" < cejet@public1.ptt.js.cn>
利 用WINDOWS 定 时 器 实 现 串 口 通 信 控 制
---- 1: 串 口 通 信 概 述
---- 在 计 算 机 外 设 中,RS-232 串 口 因 为 其 组 成 方 式 简 单, 编 程 控 制 方 便 而 成 为 最 为 应 用
广 泛 的I/O 通 道 之 一。 在 硬 件 连 接 方 面, 最 为 简 单 的 方 式 一 条 公 用 地 线 和 两 条 信 号 线 即
可 组 成 串 行 双 工 通 信 线 路。 而 在 软 件 编 程 控 制 方 面, 操 作 系 统 亦 提 供 了 对 应 的 编 程 接
口, 使 的 开 发 者 能 灵 活 的 控 制 串 口 工 作。
---- 在DOS 的 系 统 接 口 中DOS INT21H 的03H 和04H 号 功 能 调 用 为 异 步 串 行 通 信 的 接 收 和 发 送 功
能, 而BIOS INT14H 有4 组 功 能 调 用 为 串 行 通 信 服 务, 正 因 为 如 此 在DOS 中 采 用 寄 存 器 直 接 读
写、BIOS 调 用、 通 信 中 断 程 序 等 方 法 可 以 比 较 容 易 实 现 串 口 通 信。 但 是 在WINDOWS 中 由 于
WINDOWS 采 用 消 息 驱 动 和 设 备 统 一 管 理, 以 及 利 用 消 息 对 列 进 行 程 序 控 制, 所 以DOS 下 的
方 法 都 不 宜 采 用. 在WINDOWS 中 为 实 现 串 口 通 信,WINDOWS 的SDK 提 供 了 完 备 的API 函 数 和 以 中
断 方 式 驱 动 的 通 信 驱 动 程 序, 使 编 程 变 的 较 为 容 易。
---- WINDOWS 提 供 的 标 准 通 信API 函 数 很 多(win16 和win32 中 有 所 不 同), 一 般 来 说 常 用 的 有:
win16 win32 作用
OpenComm CreateFile (打开通信口);
BuildCommDCB BuildCommDCB (填充DCB数据结构);
SetCommState SetCommStae (设置通信口状态);
FlushComm PurgeComm (清除通信接收或发送缓冲区);
GetCommError ClearCommError
(恢复或取得通信口的状态);
ReadComm ReadFile (从通信接收缓冲区读字符);
WriteComm WriteFile (向通信发送缓冲区写字符);
CloseComm CloseHandle (关闭通信口);
---- 在win16 中 通 信 设 备 由 专 门 通 信 函 数 处 理, 而wn32 中 将 这 些 设 备 当 作 面 向 文 件 的 设 备 来 处 理。
---- 2:WINDOWS 定 时 器
---- WINDOWS 系 统 中, 除 键 盘 和 鼠 标 外, 定 时 器 亦 是 一 种 输 入 设 备, 使 用 定 时 器 的 方 法 比 较 简
单, 通 常 告 诉WINDOWS 一 个 时 间 间 隔, 然 后WINDOWS 以 此 时 间 间 隔 周 期 性 触 发 程 序。
---- WINDOWS 的WM_ TIMER 消 息 与BIOS 的08H、1CH 不 同, 传 统 的PC 驻 留 程 序 地 接 收 到08H 中 断 和1CH 中 断,
而WINDOWS 应 用 程 序 可 以 周 期 接 收 到WM_TIMER 消 息, 这 两 者 道 理 不 一 样. 在WINDOWS 中, 有SYSTEM.DRV 处
理 硬 件 定 时 中 断,WINDOWS 应 用 程 序 从 消 息 队 列 中 获 得WM_TIMER 消 息 而 不 会 收 到08H 硬 件 中 断,WM_TIMER
消 息 并 不 是 随 时' 插 入' 应 用 程 序, 而 是 应 用 程 序 主 动 地、 有 次 序 的 从 消 息 队 列 中 得 到 时 间 消 息。
而DOS 中 由 计 算 机 每 隔18.2s 产 生08H 中 断,08H 中 断 再 调 用1CH 中 断, 硬 件 中 断 有 一 重 要 特 征, 即 发
生 的 时 间 是 在 任 何 时 刻, 不 管 程 序 运 行 到 任 何 时 刻, 硬 件 中 断 都 可 以 插 入 程 序。
---- 3:WINDOWS 中 利 用 定 时 器 控 制 串 口 通 信
---- 在WINDOWS 中 由 串 口 进 来 的 数 据 经 过 两 个 缓 冲 区, 首 先 是 硬 件 的 接 收 缓 冲 区( 这 种 缓 冲 通 常
有8 位 或16 位), 然 后 进 入 应 用 程 序 设 置 的 串 口 接 收 缓 冲( 这 个 缓 冲 大 小 在WIN16 中 由 应 用 程 序 设
置, 而WIN32 中 由 于 串 口 作 为 面 向 文 件 设 备 处 理, 所 以 缓 冲 大 小 不 用 设 置), 数 据 从 硬 件 缓 冲 到
应 用 程 序 缓 冲 这 一 过 程 由WINDOWS 本 身 完 成, 作 为 编 程 者 不 用 考 虑 它, 应 用 程 序 只 关 心 何 时 有
数 据。 虽 然WINDOWS 提 供WM_COMMNOTIFY 消 息, 但 是 笔 者 在 使 用 中 发 现 利 用 它 控 制 比 较 麻 烦, 而 且 经
常 发 生 丢 失 数 据 现 象, 而 利 用 定 时 器 处 理 则 可 达 到 控 制 灵 活 的 效 果。
---- 在 程 序 初 始 化 串 口 以 后, 在 程 序 中 为 串 口 设 立 一 个 定 时 器, 利 用 该 定 时 器 定 时 检 查 串 口
状 态, 如 果 有 数 据 则 开 始 接 收 处 理, 否 则 该 定 时 器 消 息 无 处 理。 也 许 有 人 认 为 有 在 数 据 处 理
未 结 束 时 下 一 个 定 时 器 消 息 会 发 生 的 现 象, 对 于 这 种 情 况 除 了 加 大 定 时 器 间 隔 外 更 为 主 动 的
办 法 是 在 发 现 有 数 据 时 关 闭 定 时 器, 然 后 在 数 据 接 收 完 后 在 打 开。
---- 4.WINDOWS 通 信 实 例
---- 通 信 实 例 的 环 境 为:586/100 计 算 机; 中 文WINDOWS`95; 对 方 为 交 换 机。 通 信 参 数 为: 波 特 率9600bps;
数 据 位8 位; 停 止 位1; 无 校 验。 通 信 协 议: 帧 开 始 和 结 束 都 为0X7E0X7E, 帧 数 据 不 定 长。
编程环境:BORLANDC C++5.0,WINDOWS `95
//======================================
// FUNCTION: short InitCom()
// PURPOSE: init com1
// RETURN VALUE:
// 1-------No error
// 0-------init Error
//======================================
short InitCom()
{
if(com_select==1)
{
IDCommDevice=CreateFile("COM1",
GENERIC_READ|GENERIC_WRITE,
0,NULL,OPEN_EXISTING,0,NULL);
if(IDCommDevice==INVALID_HANDLE_VALUE)
{
MessageBox(hWnd,"OPenComm error","info",MB_OK);
CloseHandle(IDCommDevice);
return 0;
}
BuildCommDCB("COM1:9600,N,8,1",&dCB);
}else
{
IDCommDevice=CreateFile("COM2",
GENERIC_READ|GENERIC_WRITE,
0,NULL,OPEN_EXISTING,0,NULL);
if(IDCommDevice==INVALID_HANDLE_VALUE)
{
MessageBox(hWnd,"OPenComm error","info",MB_OK);
CloseHandle(IDCommDevice);
return 0;
}
BuildCommDCB("COM2:9600,N,8,1",&dCB);
}
if (SetCommState(IDCommDevice,&dCB)<0) { MessageBox(hWnd,"Read SET CommState error","info",MB_OK);
CloseHandle(IDCommDevice); return 0; } if(!PurgeComm(IDCommDevice,PURGE_RXCLEAR))
{ MessageBox(hWnd,"IN buffer flush error","info",MB_OK); CloseHandle(IDCommDevice); return 0; }
if(!PurgeComm(IDCommDevice,PURGE_TXCLEAR)) { MessageBox(hWnd,"OUT buffer flush error","info",MB_OK);
CloseHandle(IDCommDevice); return 0; } return 1; } . . . .
/****************************************** Function:JKTWndProc(HWND hDlg,UINT message, WPARAM wParam,LPARAM lParam)
Purpose: 处理交换机的信息 Message: WM_INITDIALOG---对话框初化 WM_COMMAND------所有对窗口的操作
**********************************************/
#pragma argsused LRESULT CALLBACK JKTWndProc(HWND hDlg, UINT message, UINT wParam, LONG lParam) {
LPDWORD num; short sign; char *temp_char; short x; BOOL lresult; int t_com; hdlg="hDlg;" switch (message)
{ case WM_TIMER: switch(LOWORD(wParam)) { case TIMER_REC: ClearCommError(IDCommDevice, &lpErrors,&ComStat);
if(ComStat.cbInQue!="0)" PostMessage(hDlg,WM_COMMAND ,IDP_DEALDATA,0L); break; } return 0;
case WM_COMMAND: switch(LOWORD(wParam)) { case IDP_DEALDATA: //处理交换机发的信息
for(t_com="0;t_com<" ComStat.cbInQue;t_com++)
{ num="0;" l result="ReadFile(IDCommDevice," temp_char,1,&n um,&lpOverlapped); temp_char++; }
return 0; case IDBEGIN: //开始工作 sign="InitCom(hDlg);" if(sign="=0)"
{ MessageBox(hDlg,"串口设置失败!","Information",MB_OK); return 0; }
i SetTimer(hDlg,TIMER_REC,20,NULL); return 0; } break; } return (0); } . . . . .
---- 由 于 篇 幅 有 限, 只 给 出 程 序 的 小 部 分, 由 于 水 平 有 限, 有 所 失 误 的 地 方 请 多 多 指 正。
--------------------------------------------------------------------------------
VC++下串行通信的编程方法
张 力 吕振肃 赵庆林
1 引言
串行通信程序是微机监控软件中基础、关键的一个环节,如何有效地开发通信程序,是软件开发人员所关心的话题。
Microsoft公司从1993年推出VC++1.0开始,不断升级,从 VC++1.52到目前最新的 VC++5.0,拥有大量的用户,用VC++开发
串行通信程序也随版本的不同各有特点。本文详细地介绍了用VC编写通信程序的两种方法:
. 用API(Application Programm Interface用户程序接口)函数;
. 用OLE(Object Linking and Embedding对象链接与嵌入)控件。
2 用API函数实现串行通信
API函数是Windows程序开发使用的主要函数集,其中提供了十几个专门的通信函数。对应Windows3.x和Win95及 NT开发
环境,有16位和32位通信API函数,VC++ 2.0和以前的版本都保持16位和32位两个版本,从 VC++4.0 以后,软件只支持32位
应用程序的开发。目前较多的应用是在Win95及NT的32位开发环境下,但也存在着Windows3.x下开发的16位通信程序向32位程
序的移植问题。基于这种考虑,16位通信函数也列举出来。
2.1 主要通信函数的对比
32位通信函数与16位函数的主要区别在于,32位API函数把对通信口的操作当作一个普通文件。这是因为在Windows95和NT
下,WM-COMMNOTIFY消息已被取消,操作系统已将通信设备封装起来了,系统为每个通信设备定义了用户可定义大小的读/写缓
冲区,应用程序只对读/写缓冲区操作,而不用和通信驱动程序打交道。
功能描述16位函数改进的32位函数打开通信资源OpenCommCreatFile关闭通信资源CloseCommCloseHandle读通信缓冲区ReadCommReadFile
写通信缓冲区WriteCommWriteFile设置通信事件SetCommEventSetCommMask通信错误检查GetCommErrorClearCommError
2.2 用API函数编程的过程
首先由应用向导(AppWizard)生成程序的主框架。在集成开发环境下选File/New菜单(以VC5.0为例),弹出New对话框,单击project
标签后选MFC AppWizard (exe)项,在创建的第一步选中单文档,其他五步接受缺省选项,只需编辑生成的视类.cpp文件即可。可以用两种
方法编程:一种是利用Windows的 WM-COMMNOTIFY消息,映射出该消息的处理函数,通信编码就放在该函数中,这主要适用于VC2.0及以前
的版本 ;另一种方法是直接在.CPP文件中编写通信部分,把通信当作视类的成员函数。不论用哪种方法,编程思路是相同的:设置初始
化并打开串口-配置串口-在双方间传输数据,并进行校验等处理-不需要时释放串口,供其他程序使用。
2.3 注意事项
在Windows 3.x 中,有专门的消息WM-COMMNOTIFY用于通信。在Windows95和NT下,该消息已被取消,但用户也可以用自定义的
WM-COMMNOTIFY消息,这时要注意消息映射的声明必须如下:
ON-MESSAGE (WM-COMMNOTIFY,memberFxn); 其中memberFxn是该消息的处理函数名。
Win95中新增了基于线程的多任务,当应用程序希望仅在通信设备有数据出现时才对其操作,应创建线程来监视通信设备。考虑
到通信较费时,建议使用多线程来管理通信,让通信在后台完成。在VC++的 MFC(Microsoft Foundation Classes 微软基础类库)
中提供了专门管理线程的类 CwinThread,同时VC++还提供了一些与线程有关的全局函数,如AfxBeginThread等。多线程中资源共享
时,必须借助一些Win32同步函数来协调线程的活动,避免冲突。VC的MFC也提供了几个与同步有关的类,如CSyncObject、CMutex、
CSemaphore、Cevent等。在视类的.cpp文件下可创建通信线程,并根据实际需要确定线程的优先级,合理调用 API函数以实现通信。
3 用OLE通信控件实现串行通信
用VC++的OLE控件实现串行通信,是一种简洁的编程方法。VC4.0版和VC5.0版所提供的通信控件略有不同,下面针对这两个版本,
对OLE通信控件做具体介绍。
3.1 通信控件的插入
在VC++4.0 下,首先生成一个项目,或者打开一个存在的项目。选择菜单Insert/Component…,出现Component Gallery窗口,
选中OLE Controls下的Microsoft CommControl,双击就将控件嵌入到项目文件中了。在VC++5.0下,选菜单Project/Add To Project,
将会有一子菜单, 选中 Component and Controls项(注意如果没有打开项目,Component项不会出现在子菜单项中),接着双击
Registered ActiveX Controls,在其中会找到通信控件5.0,双击之便可插入。此时打开对话框编辑器,在工具条中会看到新增的通信
控件。
3.2 在程序中使用通信控件
象所有其他OLE控件一样,通信控件也是用一系列的属性和用户接口,用户用Get…函数得到属性的当前值,用 Set…函数设置属
性值。控件提供了27个属性,大部分属性仅和Modem连接有关,现将常用的属性列举如下:
CommPort: 设置并返回通信口号,缺省值为COM1,可设置1-16个。
Settings: 设置并返回波特率、奇偶校验、数据位、停止位的字符串。其中波特率的范围为300b/s-19200b/s。
PortOpen: 设置并返回通信口的状态,同时用来打开和关闭通信口。
InputLen: 决定每次Input读入的字符个数,缺省为0,表示读取接收缓冲区的全部内容。
Input: 读入并清除接收缓冲区的字符。
InBufferCount: 返回接收缓冲区已接收的字符数,通过置0可清除接收缓冲区。
Output: 将发送的字符串或数组写到发送缓冲区。
InputMode: 定义Inpput属性获得数据的方式(VC5.0新增属性)。
Rthreshold: 设置、返回在通信控件置 ComEvRecieve并激发 OnComm事件前要接收的字符数。
SThreshold: 设置、返回通信控件置ComEvSend并激发OnComm事件前发送缓冲区中的最少字符数。
将通信控件放到对话框中,当作一个普通的控件来用。控件提供了两种处理方法:一是查询,一是事件驱动。对于较简单的通
信任务,可通过查询串口的CommEvent属性来了解最近发生的事件或错误并进行相应的处理,较复杂的通信任务就应使用事件驱动接
口。用事件驱动方法时,要定义CMSComm变量,以后针对此对象操作即可。添加消息处理函数时要用类向导ClassWizard,选中定义
的 CMSComm变量并单击,在消息栏双击 OnComm项就生成了消息处理函数。 在编写发送和接收函数时,要重点注意几个属性的用法。
比如InBufferCount属性返回接收缓冲区中已收到的字节数,查询方法可利用该属性,看是否收到了规定数目的字节。还有 Rthreshold
和 SThreshold属性,对由发送和接收激发的OnComm事件起着决定性的作用。Rthreshold 的值代表每当接收缓冲区中有该数目的字
符时就产生 OnComm事件, 这对固定长度的帧接收非常有用,可将 Rthreshold 设为帧长度,每收到一帧就会激发 OnComm事件,在消
息处理函数中添加代码就能完成接收后的处理。发送比接收简单,只不过用SThreshold属性。
概括起来,用控件实现串行通信应该遵循和用API函数编写程序相同的过程,只不过这里用控件的属性代替了API函数。
3.3 其它注意事项
VC4.0下和VC5.0下Input属性的返回值不同。在 VC4.0下Input 返回的是CString类,而在VC5.0下,返回 COleVariant类。
COleVariant类封装了VARIANT数据类型,该结构也是Output属性所要求的类型。要用好控件就必须了解 VARIANT结构, 该结构囊括
了所有常用的简单数据类型。但是结构中每次可用的数据类型只有一种,由结构的成员vt识别。例如对字符串bstrVal,vt=8,对short
类型,vt=2等等。同时注意到VC5.0控件增加了 InputMode属性,提供两种输入模式:文本和二进制。两种方式决定了Input接收的两种
数据类型。在文本方式下,Input返回值在成员bstrVal中,类型为vt=8,这和VC4.0下返回CString类是相同的。在二进制模式下,Input
接受值在安全数组parray中,类型为 vt=0x2000 ,这是编程时容易忽略的地方。一般上下位机通信时,下位机的数据是以16进制形式存
放,编写通信程序建议选择二进制输入模式。用文本模式就必须在编码时仔细考虑,否则容易丢失数据。另外,用事件驱动方法编程时
还应注意,OnComm事件在通信事件或错误发生时都会激发, 在处理函数中应将各种可能的情况列举出来分别处理,很重要的一点是及时
处理通信错误。
4 结束语
总而言之,比起繁多的API函数,通信控件不失为一种简洁有效的实现串行通信的途径。只要掌握好控件的属性,就能轻松地编写
通信程序。笔者在编写一套电力系统的实时监控软件时,用VC++5.0下的通信控件成功地实现了微机与下位机--3720ACM 仪表之间的通
信,该仪表是加拿大PML公司生产的高级电力综合监控仪表。
张 力 硕士研究生。研究方向:计算机控制与信息工程。 吕振肃 硕士,教授。从事计算机控制与信息工程、CAD等方面的教学与
研究工作。 赵庆林 硕士研究生。研究方向:计算机控制与信息工程。
作者单位: 兰州大学电子与信息科学系 甘肃.兰州(730000)
参考文献
[1] [美]Microsoft Corporation.Microsoft Win32程序员参考大全(二). 北京:清华大学出版社,1995
[2] [美]Scott Stanfield Ralph Arvesen.Visual C++ 4开发人员指南.北京:机械工业出版社,1997
串行通讯通用接口程序设计
卢望忠
串行通讯在通讯领域被广泛应用,标准的RS232接口已成为计算机、计算机外设、交换机和许多通讯设备的标准接口。微机与微机、
微机与外设、微机与程控交换机等都可以通过RS232接口进行方便的连接,以实现控制外设和传输数据等目的。
在串行接口连接中,按连接方式可分为两类:有规程连接和无规程连接。无规程连接原理比较简单,通讯双方无握手过程。一方有
数据需要发送,则立即通过串行接口发送出去,另一方被动接受。该方式虽然容易实现但数据发送的完整性却无法得到保证,在关键任
务的连接中均不采用该方式,笔者在此不再赘述。而有规程通讯过程则不同,在甲方有数据发送请求时,则向乙方发送"请求发送数据"
命令。乙方收到后,如准备就绪,则回送确认信息。甲方得到乙方的确认后方可发送数据。在大多数情况下,乙方要对收到的数据进行
校验,校验正确发送"通讯终止"命令,否则可发送"重发"命令。在实践过程中,有规程的串行连接过程通常要复杂得多,而且不同的设
备厂商都有不同的通讯规程,目前尚未形成统一的标准。即便是同一种类型的外设,在与不同品牌的设备进行连接时,也要修改原代码
来满足不同种类规程的要求,此类问题常常会困扰程序开发人员。笔者在实际工作中总结出一种简单实用的通用规程的程序设计方法,
在与多种串行通讯设备的连接中取得了满意的效果。在此简单介绍其原理,并与读者共同探讨。
在通讯的过程中,虽然规程的组合千变万化,但通讯过程中的每一步都有很多共性的东西存在。通过对多种串行通讯设备的研究,
笔者将通讯过程的每一步骤进行总结,抽象出共性的东西,在此称作通讯"步骤对象"。通讯步骤对象的定义和应用是该程序设计方法的
核心内容。每一种通讯规程都可以定义多个通讯步骤对象。整个通讯过程的的实现,就是每一个通信步骤对象依次被激活并执行各自定
义的方法的一个连贯过程。
通讯步骤对象(objComStep)一般包括如下属性:
(1)序号(StepId):每一个通讯步骤都有一个唯一的标识号,一般依据该步骤在整个规程中的执行顺序号来定义。
(2)状态(Status):每一个步骤可能有四种状态:①发送:发送命令代码;②接受:接受命令代码;③判断:对上一步骤接收到
的命令进行判断,检验接收的命令或数据是否正确;④接收数据:接收真正要传输的数据。
(3)命令代码(Command):发送或进行接收判断的控制代码。
(4)数据代码(Data):发送或接收判断的数据代码。
(5)肯定序号(AckStep):在发送成功后跳转到肯定序号指定的步骤;如接收判断成功,同样跳转到肯定行号。
(6)否定序号(NakStep):在发送不成功后跳转到否定序号指定的步骤;如接收判断不成功,同样跳转到否定行号。
(7)延迟(DelayTime):当前步骤执行完毕,延迟相应时间后跳转到下一个步骤。
(8)循环上限(MaxRunCounts):由于每一个步骤可被重复执行,对于每一个步骤有一个循环上限,如果执行次数大于该数值,
则跳转到循环跳转序号。
(9)循环跳转序号(BeyondStep):对于当前步骤被循环执行的次数超过上限时跳转到所指定的步骤。
(10)计数器(Counter):记载执行次数。
通讯步骤对象的方法定义如下:
(1)发送(Send):通过串行端口发送数据或命令代码。
(2)接收(Receive):从串行端口接收数据或命令代码。
(3)判断(Identify):判断接收数据是否正确。
(4)输出数据(OutPut):将得到的数据通过网络或消息传送给其它程序模块。
完成了对objComSetp的定义,下一步我们就能够很容易地用程序语言来将其实现。在实际编程过程中,笔者将程序总体结构分为
步骤定义模块和步骤执行模块。通过步骤定义模块定义所用规程的每一个步骤的属性,将属性信息写入配置文件。步骤执行模块按配
置文件中的信息依次创建步骤对象,由该对象执行相应的方法。步骤执行模块总体控制每个objComStep的激活顺序,直至完成整个通
讯规程。并将在通讯过程中得到的数据输出给其它程序模块。
下面是一个串行通讯的实例,笔者用上述的程序设计方法实现了PC机和NEC程控交换机之间的通讯。为了方便叙述,笔者将该程
序称为"通讯模块"。通讯模块的目的是通过串行通讯的方法来完成用PC机对NEC电话程控交换机物业管理PMS(Property Management
System)系统进行控制的任务。系统成功地实现了对交换机的控制,通过PC机实现自动开通、关闭、长途权限、电话叫早、电话留言
、电话免扰、电话设置房间状态等多种功能。
图1是该规程的状态图。
图1
说明:(1)在接收和发送之间的时间间隔是1秒;(2)在接收到NAK后,重复发送的次数为3;(3)接收方没有回应(NO ANSWER)
而重复询问的次数为15;(4)eot规程终止。
在通讯模块中,首先将通讯规程的每一步骤用步骤对象的定义方式定义该规程,其具体步骤如下:
序号 状态 命令代码 数据代码 肯定序号 否定序号 延迟 循环上限 循环跳转序号
1 发送 sa,ua,enq 2 1 1 15 9
2 接收 ack 5 3 1 1 9
3 判断 nak 1 4 1 3 9
4 判断 无回应 1 1 1 15 9
5 发送 data,bcc 6 5 1 15 9
6 接收 ack 9 7 1 9
7 判断 nak 5 8 1 3 9
8 判断 无回应 5 5 1 15 9
9 发送 eot 结束 0 1
完成上述配置后,将信息写入配置文件或数据库。在通讯模块执行时,读入该配置信息 ,依次生成通讯步骤对象。对象方法的
执行过程即是通讯规程的执行行过程。在实际编程中可将该模块与其它程序连接编译成EXE文件或生成独立的DLL文件供其它通讯程
序调用,均可取得满意的效果。在其它类型的通讯、消息传递、网络数据传输中均可用该思路写出通用的接口程序,解决异种机型连
接的通讯问题。
VC在win95下编写用于串行通讯的程序
------作者 清华BBS 贝贝
既然有这么多人问这个文体,贝贝就给个Visual C++ 4.2写的
Window 95串口通讯函数集合(只适用于32位)
需要说明的是:这是我程序的一部分,因此有一些与具体应用无关的部分。
但我觉得关键是原理,而不是程序本身.后面有些使用介绍,帮助理解这长的程序。
头文件(.H)
#include "StdAfx.h"
#define GWL_PGPSINFO 0
#define GPSEXTRABYTES sizeof( LONG )
#define MAXPORTS 4
#define CN_SEND WM_USER+100
#define RXQUEUE 4096
#define TXQUEUE 4096
// cursor states
#define CS_HIDE 0x00
#define CS_SHOW 0x01
// Flow control flags
#define FC_DTRDSR 0x01
#define FC_RTSCTS 0x02
#define FC_XONXOFF 0x04
// ascii definitions
#define ASCII_BEL 0x07
#define ASCII_BS 0x08
#define ASCII_LF 0x0A
#define ASCII_CR 0x0D
#define ASCII_XON 0x11
#define ASCII_XOFF 0x13
// data structures
typedef struct tagGPSINFO
{
HANDLE idComDev;
BYTE bPort;
BOOL fConnected;
BYTE bByteSize,bParity,bStopBits;
DWORD dwBaudRate;
HANDLE hPostEvent,hWatchThread,hWatchEvent;
HWND hTermWnd;
DWORD dwThreadID;
OVERLAPPED osWrite,osRead;
} GPSINFO, *PGPSINFO ;
#define COMDEV( x ) (x -> idComDev)
#define PORT( x ) (x -> bPort)
#define CONNECTED( x ) (x -> fConnected)
#define BYTESIZE( x ) (x -> bByteSize)
#define PARITY( x ) (x -> bParity)
#define STOPBITS( x ) (x -> bStopBits)
#define BAUDRATE( x ) (x -> dwBaudRate)
#define POSTEVENT( x ) (x -> hPostEvent)
#define HTHREAD( x ) (x -> hWatchThread)
#define THREADID( x ) (x -> dwThreadID)
#define WRITE_OS( x ) (x -> osWrite)
#define READ_OS( x ) (x -> osRead)
// function prototypes (private)
LRESULT NEAR CreateGPSInfo(HWND,BYTE nPort=1);
BOOL NEAR DestroyGPSInfo();
int NEAR ReadCommBlock(LPSTR,int);
BOOL NEAR WriteCommBlock(LPSTR,DWORD);
BOOL NEAR OpenConnection();
BOOL NEAR SetupConnection();
BOOL NEAR CloseConnection();
// function prototypes (public)
DWORD FAR PASCAL CommWatchProc(LPSTR);
具体实现请看下文(为了这文章,我都段线2次了)
CPP实现部分:
#include "StdAfx.h"
#include "Com.h"
HWND hGPSWnd=NULL;
PGPSINFO npGPSInfo=NULL;
LRESULT NEAR CreateGPSInfo(HWND hWnd,BYTE nPort)
{
if (NULL==(npGPSInfo=(PGPSINFO)LocalAlloc(LPTR,sizeof(GPSINFO))))
return ((LRESULT)-1) ;
hGPSWnd=hWnd;
COMDEV(npGPSInfo)=0;
CONNECTED(npGPSInfo)=FALSE;
PORT(npGPSInfo)=nPort;
BAUDRATE(npGPSInfo)=CBR_9600;
BYTESIZE(npGPSInfo)=8;
PARITY(npGPSInfo)=NOPARITY;
STOPBITS(npGPSInfo)=ONESTOPBIT;
WRITE_OS(npGPSInfo).Offset=0;
WRITE_OS(npGPSInfo).OffsetHigh=0;
READ_OS(npGPSInfo).Offset=0;
READ_OS(npGPSInfo).OffsetHigh=0;
// create I/O event used for overlapped reads / writes
READ_OS(npGPSInfo).hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
if (READ_OS(npGPSInfo).hEvent==NULL)
{ LocalFree( npGPSInfo ) ;
return ( -1 ) ;
}
WRITE_OS(npGPSInfo).hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
if (NULL==WRITE_OS(npGPSInfo).hEvent)
{ CloseHandle(READ_OS(npGPSInfo).hEvent);
LocalFree(npGPSInfo) ;
return (-1) ;
}
return ( (LRESULT) TRUE ) ;
}
BOOL NEAR DestroyGPSInfo()
{
if (!npGPSInfo) return (FALSE);
if (CONNECTED(npGPSInfo)) CloseConnection();
CloseHandle(READ_OS(npGPSInfo).hEvent);
CloseHandle(WRITE_OS(npGPSInfo).hEvent);
CloseHandle(POSTEVENT(npGPSInfo));
LocalFree(npGPSInfo);
return (TRUE);
}
BOOL NEAR OpenConnection()
{
char szPort[15];
BOOL fRetVal;
HCURSOR hOldCursor,hWaitCursor;
HANDLE hCommWatchThread;
DWORD dwThreadID;
COMMTIMEOUTS CommTimeOuts;
if (!npGPSInfo) return (FALSE);
hWaitCursor=LoadCursor(NULL,IDC_WAIT) ;
hOldCursor=SetCursor(hWaitCursor) ;
wsprintf(szPort,"COM%d",PORT(npGPSInfo));
if
((COMDEV(npGPSInfo)=CreateFile(szPort,GENERIC_READ|GENERIC_WRITE,
0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,
NULL))==(HANDLE)-1)
return ( FALSE ) ;
else
{ SetCommMask(COMDEV(npGPSInfo),EV_RXCHAR);
SetupComm(COMDEV(npGPSInfo),4096,4096);
PurgeComm(COMDEV(npGPSInfo),PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_R
XCLEAR);
CommTimeOuts.ReadIntervalTimeout=0xFFFFFFFF;
CommTimeOuts.ReadTotalTimeoutMultiplier=0;
CommTimeOuts.ReadTotalTimeoutConstant=1000;
CommTimeOuts.WriteTotalTimeoutMultiplier=0;
CommTimeOuts.WriteTotalTimeoutConstant=1000;
SetCommTimeouts(COMDEV(npGPSInfo),&CommTimeOuts);
}
fRetVal=SetupConnection();
if (fRetVal)
{ CONNECTED(npGPSInfo)=TRUE;
if
(NULL==(hCommWatchThread=CreateThread((LPSECURITY_ATTRIBUTES)NULL,
0,(LPTHREAD_START_ROUTINE)CommWatchProc,
(LPVOID)NULL,0,&dwThreadID)))
{ CONNECTED(npGPSInfo)=FALSE;
CloseHandle(COMDEV(npGPSInfo));
fRetVal=FALSE;
}
else
{ THREADID(npGPSInfo)=dwThreadID;
HTHREAD(npGPSInfo)=hCommWatchThread;
EscapeCommFunction(COMDEV(npGPSInfo),SETDTR);
}
}
else
{ CONNECTED(npGPSInfo)=FALSE;
CloseHandle(COMDEV(npGPSInfo));
}
SetCursor(hOldCursor);
return (fRetVal);
}
BOOL NEAR SetupConnection()
{ BOOL fRetVal;
DCB dcb;
if (!npGPSInfo) return(FALSE);
dcb.DCBlength=sizeof(DCB);
GetCommState(COMDEV(npGPSInfo),&dcb);
dcb.BaudRate=BAUDRATE(npGPSInfo);
dcb.ByteSize=BYTESIZE(npGPSInfo);
dcb.Parity=PARITY(npGPSInfo);
dcb.StopBits=STOPBITS(npGPSInfo);
dcb.fOutxDsrFlow=FALSE;
dcb.fDtrControl=DTR_CONTROL_ENABLE;
dcb.fOutxCtsFlow=FALSE;
dcb.fRtsControl=RTS_CONTROL_ENABLE;
dcb.fInX=dcb.fOutX=FALSE;
dcb.fBinary=TRUE;
dcb.fParity=TRUE;
fRetVal=SetCommState(COMDEV(npGPSInfo),&dcb);
return (fRetVal);
}
BOOL NEAR CloseConnection()
{
if (!npGPSInfo) return(FALSE);
CONNECTED(npGPSInfo)=FALSE;
SetCommMask(COMDEV(npGPSInfo),0);
while(THREADID(npGPSInfo)!=0);
EscapeCommFunction(COMDEV(npGPSInfo),CLRDTR);
PurgeComm(COMDEV(npGPSInfo),PURGE_TXABORT|PURGE_RXABORT|
PURGE_TXCLEAR|PURGE_RXCLEAR);
CloseHandle(COMDEV(npGPSInfo));
return (TRUE);
}
int NEAR ReadCommBlock(LPSTR lpszBlock,int nMaxLength)
{
BOOL fReadStat ;
COMSTAT ComStat ;
DWORD dwErrorFlags;
DWORD dwLength;
DWORD dwError;
if (!npGPSInfo) return(FALSE);
ClearCommError(COMDEV(npGPSInfo),&dwErrorFlags,&ComStat);
dwLength=min((DWORD)nMaxLength,ComStat.cbInQue);
if (dwLength>0)
{ fReadStat=ReadFile(COMDEV(npGPSInfo),lpszBlock,
dwLength,&dwLength,&READ_OS(npGPSInfo));
if (!fReadStat)
{ if (GetLastError()==ERROR_IO_PENDING)
{ OutputDebugString("\n\rIO Pending");
while(!GetOverlappedResult(COMDEV(npGPSInfo),&READ_OS(npGPSInfo),&dwLength,TR
UE))
{ dwError=GetLastError();
if(dwError == ERROR_IO_INCOMPLETE)
continue;
}
}
else
{ dwLength=0;
ClearCommError(COMDEV(npGPSInfo),&dwErrorFlags,&ComStat);
}
}
}
return ( dwLength ) ;
}
BOOL NEAR WriteCommBlock(LPSTR lpByte,DWORD dwBytesToWrite)
{ BOOL fWriteStat;
DWORD dwBytesWritten;
DWORD dwErrorFlags;
DWORD dwError;
COMSTAT ComStat;
if (!npGPSInfo) return(FALSE);
fWriteStat=WriteFile(COMDEV(npGPSInfo),lpByte,dwBytesToWrite,
&dwBytesWritten,&WRITE_OS(npGPSInfo));
if (!fWriteStat)
{ if(GetLastError()==ERROR_IO_PENDING)
{ while(!GetOverlappedResult(COMDEV(npGPSInfo),
&WRITE_OS(npGPSInfo),&dwBytesWritten,TRUE))
{ dwError=GetLastError();
if(dwError == ERROR_IO_INCOMPLETE)
continue;
else
{
ClearCommError(COMDEV(npGPSInfo),&dwErrorFlags,&ComStat);
break;
}
}
}
else
{
ClearCommError(COMDEV(npGPSInfo),&dwErrorFlags,&ComStat);
return ( FALSE );
}
}
return ( TRUE ) ;
}
DWORD FAR PASCAL CommWatchProc(LPSTR)
{ DWORD dwEvtMask;
OVERLAPPED os;
int nLength;
BYTE abIn[1024];
memset(&os,0,sizeof(OVERLAPPED));
// create I/O event used for overlapped read
os.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
if (os.hEvent==NULL)
{ MessageBox(NULL,"Failed to create event for thread!","GPS
Error!",MB_ICONEXCLAMATION|MB_OK);
return ( FALSE ) ;
}
if (!SetCommMask(COMDEV(npGPSInfo),EV_RXCHAR)) return (FALSE);
while (CONNECTED( npGPSInfo))
{ dwEvtMask=0 ;
WaitCommEvent(COMDEV(npGPSInfo),&dwEvtMask,NULL);
if ((dwEvtMask&EV_RXCHAR)==EV_RXCHAR)
{ do
{ if
(nLength=ReadCommBlock((LPSTR)abIn,1024))
{
//WriteCommBlock((LPSTR)abIn,nLength );
*(abIn+nLength)=0;
::SendMessage(hGPSWnd,CN_SEND,nLength,(LONG)(LPSTR)abIn);
}
}
while ((nLength>0)&&(CONNECTED( npGPSInfo)));
}
}
CloseHandle(os.hEvent);
THREADID(npGPSInfo)=0;
HTHREAD(npGPSInfo)=NULL;
return(TRUE);
}
就这些了,希望能对问这些问题的朋友有所帮助!
一般使用的顺序是:
CreateGPSInfo(被通知的窗口句柄,串口端口号1或2);
OpenConnection();//建立联结它会调用SetupConnection
DestroyGPSInfo();//解除联结它会调用CloseConnection
可以用ReadCommBlock/WriteCommBlock来读/写串口
CommWatchProc是监视串口的线程,由OpenConnection建立
当串口有数据来的时侯,它会通知'被通知的窗口句柄'的窗口数据传到的消息(自定义的)
SendMessage(hGPSWnd,CN_SEND,nLength,(LONG)(LPSTR)abIn);
好了,文章结束了!希望能帮助你!