荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: backey (Yagami Makoto.), 信区: Visual
标  题: 用Visual C++开发Windows环境下串行通信程序
发信站: BBS 荔园晨风站 (Sun Dec  5 19:07:29 1999), 转信


摘要:本文详细介绍了在Visual C++ 5.0中使用串行通信控件时一些常见的问题及其解决办
法,并给出了一个较实用的通信示例程序。
关键词:串行通信  Windows  Visual C++ 5.0  ActiveX控件

1  引言

2 Visual C++ 5.0中串口控件的新特征
在Visual C++ 5.0中,串行通信的控件不再叫做OCX控件,而是改名为ActiveX控件,通用于
Visual Basic、Delphi以及诸多Internet应用程序中。与Visual C++
4.x中的串口控件相比,其最显著的变化一是将GetInput()函数的返回类型改为VARIANT(以
前为CString);二是增加了一个新的.粜裕篒nputMode(输入方式),可以设置为
comInputModeText(0)或comInputModeBinary(1),分别表示以文本方式(字符串)或以二进
制方式读入接收到的字节串
。这两大改进为我们解决下面将要介绍的某些问题提供了强有力的武器。

3 使用串口控件时一些常见问题的产生原因及解决办法
概括起来,在使用串口控件时,经常会遇到以下一些问题:

3.1  如何发送00H
在《用…》文给出的示例程序中,需要发送的是一个简单的字符串,所以使用了一个
CString类型的变量来存储它,然后使用COleVariant类的构造函数将之转换为SetOutput()
函数所需要的VARIANT类型的参数(VARIANT和COleVariant可以通用)。由于CString是以
00H作为字符串结束标志的
,所以如果要发送的串中含有00H,则00H及其后的字节都将被CString舍弃,导致只有部分
字节被发送。要解决这个问题,必须使用其它类型的变量来存储待发送的字节串。
查看一下COleVariant类的构造函数可知,COleVariant()可以接受多种类型的参数,除了
CString外,比较常用的还有BYTE、short、long、float、double及CByteArray等。由于通
过串口发送的数据常常是一串二进制字节,因此CByteArray将是最合适的一个数据类型。
CByteArray用于存储?
桓龆腂YTE数组(可以动态改变数组大小)。当待发送的字节串准备好后,我们可以定
义一个CByteArray的临时变量,然后使用SetSize()函数设置其大小(包含的字节数),并
将相应的字节值存入该数组,最后利用COleVariant()转换为COleVariant类型,即可利用
SetOutput()函数发?
统鋈ィㄏ昙纠蠺ransmitt()函数)。

3.2  中文Windows下通信有时会出错
有些编制好的通信程序在西文Windows下运行一直很正常,但在中文Windows运行却常常出错
,特别是一些大于80H的字节,接收和发送时会出现误码。产生这个问题的主要原因是由于
Windows使用不同类型的字符串而导致的。
Windows使用两种类型的字符串,即ANSI字符串和Unicode字符串。所有16位的应用程序都使
用ANSI字符串;而32位应用程序既可以使用ANSI字符串,也可以使用Unicode字符串。ANSI
以unsigned char类型存储字符串,每个字节表示一个不同的字符(与DOS相同);而
Unicode以unsigned
short类型存储字符串,每两个字节表示一个不同的字符。使用Unicode字符串的一个好处就
是便于应用程序的本地化(对我们来说就是汉化),因为汉字就是使用的两个字节的内码(
大于80H的ASCII码多被汉字内码所使用)。但在Windows
95中,Windows系统调用使用的是ANSI字符串,因此在中文Windows下的应用程序中,我们必
须进行Unicode 字符串和ANSI字符串之间的相互转换。
在Visual C++ 4.x中,读入串口接收数据的函数GetInput()返回的是CString类型,如没有
进行上述转换,就有可能导致误码出现。前面提到,在Visual C++
5.0中,GetInput()返回的是VARIANT类型,由于VARIANT结构中包含一个多种数据类型的联
合(union),使得我们可以避开使用字符串。同时Visual C++ 5.0串口控件的另一个新属性
InputMode可以将输入方式设置为Binary(二进制),从而为我们避开使用字符串进一步铺
平了道路。

3.3  不知如何使用VARIANT数据类型
有不少读者对VARIANT这个新的数据类型大感头疼。SetOutput()函数中需要的VARIANT参数
还可以使用COleVariant类的构造函数简单生成,现在GetInput()函数的返回值也成了
VARIANT类型,很多读者往往不知该如何从返回的值中提取有用的内容。
VARIANT及由之而派生出的COleVariant类主要用于在OLE自动化中传递数据。实际上
VARIANT也只不过是一个新定义的结构罢了,它的主要成员包括一个联合体及一个变量。该
联合体由各种类型的数据成员构成,而该变量则用来指明联合体中目前起作用的数据类型。
我们所关心的接收到的数
据就存储在该联合体的某个数据成员中。
该联合体中包含的数据类型很多,从一些简单的变量到非常复杂的数组和指针。由于通过串
口接收到的内容常常是一个字节串,我们将使用其中的某个数组或指针来访问接收到的数据
。这里推荐给大家的是指向一个SAFEARRAY类型变量的指针parray。新的数据类型
SAFEARRAY正如其名字一样
,是一个“安全数组”,它能根据系统环境自动调整其16位或32位的定义,并且不会被OLE
改变(某些类型如BSTR在16位或32位应用程序间传递时会被OLE翻译从而破坏其中的二进制
数据)。大家无须了解SAFEARRAY的具体定义,只要知道它是另外一个结构,其中包含一个
(void
*)类型的指针pvData,其指向的内存就是存放有用数据的地方。
简而言之,从GetInput()函数返回的VARIANT类型变量中,找出parray指针,再从该指针指
向的SAFEARRAY变量中找出pvData指针,就可以向访问数组一样取得所接收到的数据了。(
详见示例程序中OnCommMscomm()及SaveData()函数)

4   一个实用的通信示例程序
下面给出一个完整的通信示例程序,该程序能在两台计算机之间根据指定要求进行数据通信
。每台计算机既能发送也能接收各种二进制数据,并且采取了事件驱动的接收方式(
Event-drive,类似于DOS中的中断)。其中的许多程序代码具有通用性,大家可以直接或稍
加改动后用于自己的应
用程序中。

4.1  程序功能简介
从任一台机上可以向另一台机发送一命令帧,请求对方发回一数据帧。该数据帧中包含的有
效数据长度(字节数)、起始值及相应两数据之间的增量都可以由用户指定,并通过命令帧
传递过来。收到命令帧的计算机发送指定要求的数据帧到对方。接收和发送的内容都显示在
对话框中(见图
2、3)。

4.2  通信规约
命令帧格式如下:
帧长度  特征码00H       起始值  字节数  增量    校验和
其中校验和为前面所有字节的无进位累加和,用于检验通信是否出错。对命令帧,帧长度=
6。
数据帧格式如下:
帧长度  特征码0FFH      指定起始值、字节数及增量的字节串        校验和
其中校验和同命令帧。对数据帧来说,帧长度=命令帧中指定的字节数+3。

4.3  具体实现过程
第一步,启动Visual C++ 5.0,新建一个基于对话框的应用程序TxRx。
第二步,插入串口控件。在Visual C++ 5.0版中插入控件的方法与4.x中不同,具体步骤是
:选择Project菜单下Add To Project子菜单中的Components and Controls…选项,在随后
的对话框中双击Registered ActiveX
Controls项,则所有注册过的ActiveX控件出现在列表框中。选择Microsoft
Communications Control, version 5.0,这就是新的串口控件,单击Insert按钮将它插入
到我们的Project中来。
接下来的工作就是要改造应用程序的主对话框IDD_TXRX_DIALOG,删掉其中的静态文本及“
确定”按钮,将“取消”按钮改为“退出”,然后增加新的静态文本、编辑框和按钮控件(
如图1所示),并为它们添加相应的变量(表1),
图1  定制应用程序主对话框

表1 主对话框中新增控件及相应变量
控件    控件ID  变量名  变量类型
按钮    IDC_TRANSMITT   发送按钮
Edit    IDC_TXDATA      m_TxData        CString
Edit    IDC_RXDATA      m_RxData        CString
Edit    IDC_INIT        m_Init  BYTE
Edit    IDC_LENGTH      m_Length        BYTE
Edit    IDC_STEP        m_Step  BYTE
串口    IDC_MSCOMM      m_ComPort       CMSComm
第四步,需要修改TxRxDlg.cpp文件,添加有关程序代码。具体步骤如下:
首先,在文件头部第一条注释行前加入如下常数定义及全局变量说明。
#define comEvReceive            2
#define comInputModeBinary      1
unsigned char  RcvData[300];            //接收数据存储区
static   int      SavePointer=0;                //数据存储指针
unsigned char  TxData[300];             //发送数据存储区
其次,需要初始化串口参数。在OnInitDialog()函数中TODO语句后加入以下初始化代码:
// TODO: Add extra initialization here
m_ComPort.SetCommPort(1);               //选择COM1
if (!m_ComPort.GetPortOpen())           //打开串口
m_ComPort.SetPortOpen(TRUE);
//设置输入方式为二进制方式
m_ComPort.SetInputMode(comInputModeBinary);
//设置波特率等参数
m_ComPort.SetSettings("2400,n,8,1");
m_ComPort.SetRThreshold(1);
/* 将Rthreshold参数设为1表示每当串口接收缓冲区中有多于或等于1个字符时将引发一个
关于comEvReceive(接收数据)的OnComm事件 */
m_ComPort.SetInputLen(0);
//先预读缓冲区以清除残留数据
m_ComPort.GetInput();
接着,为发送按钮IDC_ TRANSMITT添加BN_CLICKED(鼠标单击)消息处理函数
OnTransmitt() ,内容如下:
void CTxRxDlg::OnTransmitt()
{
    UpdateData(TRUE);   //获取用户输入的数据
    TxData[0]=0x06;     //命令帧长度为6
    TxData[1]=0x00;     //命令帧特征码为00H
    TxData[2]=m_Init;   //指定数据初值
    TxData[3]=m_Length; //指定数据长度值
    TxData[4]=m_Step;   //指定数据增量值
    Transmitt();                 //发送命令帧
}
接着,为类CTxRxDlg添加成员函数void Transmitt(void),其功能是将存储在TxData数组中
的内容通过串口发送出去。
void CTxRxDlg::Transmitt()
{   int          i;
    unsigned char  Sum=0,Count;
    CByteArray   array;
    char                 sOutput[10];
    Count=TxData[0];            //帧长度
    for(i=0; i<Count-1; i++)
         Sum+=TxData[i];                //计算校验和
    TxData[Count-1]=Sum;        //存储校验和
    array.RemoveAll();          //清空数组
    array.SetSize(Count);               //设置数组大小为帧长度
    for(i=0; i<Count; i++)              //把待发送数据存入数组
          array.SetAt(i,TxData[i]);
    // 打开串口并发送数据
    if (!m_ComPort.GetPortOpen())
           m_ComPort.SetPortOpen(TRUE);
    m_ComPort.SetOutput(COleVariant(array));
    // 在对话框中显示发送出去的数据
    m_TxData="";
    for(i=0; i<Count; i++)
    {     sprintf(sOutput,"%02X, ",TxData[i]);
          m_TxData+=sOutput;
    }
    UpdateData(FALSE);          //更新对话框
}
至此,关于发送数据的代码已添加完毕,读者可以从中看到使用CByteArray来发送二进制字
符串是何等的容易!
下面,我们将添加有关接收和处理数据的代码。为使用事件驱动的接收方式,我们需要为串
口控件IDC_MSCOMM添加处理OnComm事件的函数(通过ClassWizard)OnCommMscomm(),内容
如下:
void CTxRxDlg::OnCommMscomm()
{
    VARIANT     vResponse;
    int         k;
    if (m_ComPort.GetCommEvent()==comEvReceive)
    {    //判断是否comEvReceive事件
         k=m_ComPort.GetInBufferCount();
         If (k>0)
         {
            m_ComPort.SetInputLen(k);
//读取接收到的数据
            vResponse=m_ComPort.GetInput();
       SaveData(k,(unsigned char *) vResponse.parray->pvData);  //保存接收到的数

        }
    }
}
在本段程序中,我们先定义了一个VARIANT类型的变量vResponse,利用它读取串口数据后,
再通过它获取parray指向的SAFEARRAY对象中的pvData指针,则可以访问所需数据。下面我
们看一看在SaveData()函数中如何使用该指针获取数据。
为类CTxRxDlg添加成员函数void SaveData(int Count, unsigned char *pbVal),内容如下

void CTxRxDlg::SaveData(int Count, unsigned char *pbVal)
{
    int i;
    for(i=0; i<Count; i++)
          RcvData[SavePointer+i]=pbVal[i];  //存储数据
SavePointer+=Count;
//如接收计数指针超过一帧长度,则处理该帧数据
    if (SavePointer>=RcvData[0])
          ProcessData();
}
最后剩下的工作就是处理收到的数据。对于广大拥护来说,这一项工作可以充分利用以前在
DOS下编写的代码。为简化起见,本程序将首先检测收到的一帧数据是否正确(通过校验和
),然后判断是否是命令帧(通过特征码),是则按要求发送相应的数据帧。最后将接收到
的内容显示在对?
翱蛑小?
为类CTxRxDlg添加成员函数void  ProcessData(void),其内容如下:
void CTxRxDlg::ProcessData()
{
    int         I, Count;
    unsigned char  Sum=0;
char             sInput[10];
Count=RcvData[0]        //帧长度
    for(i=0; i<Count-1; i++)
         Sum+=RcvData[i];       //计算校验和
    if (Sum!=RcvData[Count-1])
    {                           //校验和不正确
          MessageBox("收到数据有误:校验和不正确!");
          m_ComPort.SetOutBufferCount(0);
          SavePointer=0;        //清除缓冲区中全部数据
    }
    else
    {
         int                    Length;
         unsigned char  AL;
         MessageBeep(0xFFFFFFFF);
         if (RcvData[1]==0)                //是否命令帧
         {
            TxData[0]=RcvData[3]+3; //数据帧长度
            TxData[1]='\xFF';      //数据帧特征码
            TxData[2]=RcvData[2];  //数据起始值
            Length=RcvData[3];     //数据长度
            AL=RcvData[4];         //数据增量
//根据起始值和增量计算其它数据
for (i=1; i<Length; i++)
   TxData[i+2]=TxData[i+1]+AL;
            Transmitt();                   //发送数据帧
          }
          m_RxData="";          //显示接收到的数据
          for(i=0; i<Count; i++)
          {
            sprintf(sInput,"%02X, ",RcvData[i]);
            m_RxData+=sInput;
          }
          UpdateData(FALSE);
        //从接收存储区中清除已处理过的一帧数据
          for(i=0; i<SavePointer-Count; i++)
                RcvData[i]=RcvData[Count+i];
           SavePointer-=Count;
    }
}
现在,我们已完成了全部编程工作。可以看到,并没有花很大的工作量,而我们已编写出了
一个功能较强的串口通信程序。
4.4  程序运行演示
编译、连接以上程序,将生成的可执行文件TxRx.exe拷贝到两台计算机中(如该计算机没有
安装Visual
C++,则还需要把相关的动态链接库拷贝到其Windows目录下的System子目录中,例如
mscomm32.ocx、msvcrt.dll、mfc42.dll等),用一根电缆将两台计算机的串口连接起来(
只需三根信号线:Gnd直接相连、Txd和Rxd对接即可),运行TxRx应用程序,在其中一台计
算机上输入起始值、字节
数和增量等数值,然后按下“发送”按钮,可以看到命令和数据在两台机之间传递的过程(
图2、3)。
图2  发送命令端运行结果
图3  发送数据端运行结果

参考文献
1.穆宗学,章江,肖蓉,Visual C++ 4.2编程实践指要,中国铁道出版社,1997年。

※ 来源:·BBS 水木清华站 bbs.net.tsinghua.edu.cn·[FROM: 166.111.60.85]
--
※ 修改:·backey 於 Dec  5 19:08:07 修改本文·[FROM: 192.168.28.220]
※ 转载:·BBS 荔园晨风站 bbs.szu.edu.cn·[FROM: 192.168.28.220]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店