一:在Visual C++ 6.0集成开发环境下,可以通过三种方法实现串口通信
1)运用MFC函数进行编程实现串口通信
2)运用MSComm控件进行编程实现串口通信
3)运用VC++运行库函数实现串口通信
在这三种方法中,MSComm—( Microsoft Communication Control )控件因使用简便而广受应用,它是Microsoft公司出品的一种ActiveX 控件。控件描述:一个MSComm控件对应一个串行端口,应用程序访问了多少个串行端口就必须使用多少个MSComm控件;用该控件实现串口通信时,实际上是调用了API函数,但程序员不必知道MSComm控件调用API函数的具体细节,而只需了解MSComm控件具有哪些属性和如何添加事件处理代码即可实现串口的操作。下面对MSComm控件常用属性做个小小总结。
二:MSComm控件属性及事件(出自Visual C++串口通信与工程应用实践 第八章)
MSComm控件是微软公司开发的专门用于串行通信的控件,它是高级语言编写的串行通信程序和PC串口之间的桥梁,通过这个桥梁,使得开发串口通信程序的工作变得更容易。设置控件的几个属性,然后添加控件的事件响应代码,就可以使串口按照要求进行工作。MSComm控件的大部分属性都是可读可写的,当对其进行写入操作时,是在设置属性的新值;当读属性时,可以读出该属性的当前设置值。在Visual C++中,对控件属性的操作都是通过特定的函数来实现的,这些函数都是CMSComm类的成员函数,当声明了一个CMSComm类的实例后,就可以对该实例使用"."操作符加成员函数的方法来访问控件的属性了,以下属性是每次通信中使用频率最高的属性,通常每次通信都要进行设置。
1.CommPort属性
通过设置该属性值,可以决定串口通信使用的串口编号。读取该属性值可获取当前程序使用的串口编号。使用如下两个函数来操作
void SetCommPort(short nNewValue); //设置串口编号
short GetCommPort(); //返回正在使用的串口编号
nNewValue可以设置为1~16的任何数值(默认值为1),对应使用的串口号为COM1~COM16。但是若设置的串口号实际并不存在,则在打开端口时,MSComm控件会产生“设备无效”的错误
例如:myComm.SetCommPort(2); //表示设置控件myComm的通信端口为串口COM2
提示:必须在打开端口之前设置CommPort属性
2.Settings属性
设置或者获取串行通信的通信参数。通信参数包括波特率、奇偶校验类型、数据位数及停止位数等4个参数。使用如下两个函数操作
void SetSettings(LPCTSTR lpszNewValue); //设置通信参数,由lpszNewValue来表示
CString GetSettings(); //读取当前串口通信参数
使用字符串类型来设置该属性,lpszNewValue的格式为:"BBBB,P,D,S"
其中
BBBB为波特率,有效的数值为110、300、600、1200、2400、9600(默认值)、14400、19200、28800、38400、56000等;
P为奇偶校验类型,可用类型为:E(偶校验)、M(标记校验)、N(默认值,无校验)、O(奇校验)、S(空格校验);
D为数据位数,可以取值为4、5、6、7、8(默认值);
S为停止位数,可以取值为1(默认值)、1.5和2。
myComm.SetSettings("19200,O,8,1"); //设置myComm控件的通信参数,波特率为19200,奇校验,使用8位数据位及1位停止位
提示:通信双方的Settings参数设置必须相同,否则无法进行通信。
3.PortOpen属性
设置该属性值可以打开或关闭串口。使用如下两个函数来操作该属性:
void SetPortOpen(BOOL bNewValue); //打开或者关闭串口
BOOL GetPortOpen(); //读取端口的状态,即状态为打开还是关闭
由bNewValue决定打开还是关闭串口,其值为TRUE时打开串口,为FALSE时关闭串口。在使用串口之前应通过设置该属性打开串口,而当退出串口通信时,应该关闭串口,以释放程序占用的串口资源。
4.Input属性
通过操作该属性值,可以从串口通信输入缓冲区获取数据。使用如下函数来操作该属性:
VARIANT GetInput();
执行该函数后,返回并删除接收缓冲区中的数据。默认情况下读取缓冲区中全部的内容,若设置InputLen属性值大于0,则读取的字符数量由InputLen属性值决定。示例程序如下所示:
VARIANT in1;
in1 = myComm.GetInput(); //将输入缓冲区中内容读入in1变量中
提示:使用GetInput()函数返回的内容是VARIANT类型的数据,这需要通过一定处理后,才能转换为常见的字符串类型或数值类型
5.Output属性
通过该属性,向串口通信输出缓冲区写入数据,然后通过串口将数据发送出去。使用如下函数来操作该属性:
void SetOutput(const VARIANT& newValue); //该函数表示将newValue的内容写入输出缓冲区。示例程序如下所示:
CString aa;
aa = "at\r\n";
myComm.SetOutput(COleVariant(aa)); //将aa字符串内容写入输出缓冲区
提示:使用SetOutput()函数写入输出缓冲区的内容必须是VARIANT类型的数据。上例中使用COleVariant()转换函数将CString类型的aa数据转换为VARIANT类型的数据。
三:与输入操作有关的属性
以下属性都与输入操作有关。这些属性都具有默认值,若对输入及输入缓冲区不进行特定的操作,通常可不设置该属性。
1.InputLen属性
当使用GetInput()函数从输入缓冲区中读取数据时,InputLen属性决定了一次读取的字节数。使用如下函数来操作该属性:
void SetInputLen(short nNewValue); //设置从输入缓冲区中一次读出的字节数
short GetInputLen(); //获取当前从输入缓冲区中一次读出的字节数
提示:该属性值为0(默认值)时,读取整个缓冲区中的内容。
2.InputMode属性
该属性用于设置或读出GetInput()函数从输入缓冲区中读取数据时的读取方式。使用如下函数来操作该属性:
void SetInputMode(long nNewValue); //设置从输入缓冲区中读取数据的方式
long GetInputMode(); //获取当前从输入缓冲区中读取数据的方式
该属性值为0(默认值)时,表示以文本方式从输入缓冲区中读取数据;该属性值为1时,则以二进制方式从输入缓冲区中读取数据
3.InBufferSize属性
使用该属性来设置或读出串行通信输入缓冲区的大小。使用如下函数来操作该属性:
void SetInBufferSize(short nNewValue); //设置输入缓冲区的大小
short GetInBufferSize(); //读出输入缓冲区的大小设置值
该属性的默认值是1024,单位是字节,即输入缓冲区可缓冲1024字节数据。
4.InBufferCount属性
使用GetInBufferCount()函数可以返回当前输入缓冲区中可以读取的有效数据个数,以字节为单位。使用如下函数来操作该属性:
void SetInBufferCount(short nNewValue); //设置当前输入缓冲区中待读取数据的个数
short GetInBufferCount(); //获取当前输入缓冲区中待读取数据的个数
使用SetInBufferCount()函数,参数nNewValue设为0时,可将输入缓冲区清空。除0以外的参数都将导致函数出错。
5.RThreshold属性
该属性代表一个阈值。当接收缓冲区中的字符数达到该阈值时,MSComm控件就会产生OnComm事件,并且CommEvent属性会被设置为ComEvReceive,即接收事件。使用如下函数来操作该属性:
void SetRThreshold(short nNewValue); //设置接收缓冲区产生OnComm事件的阈值
short GetRThreshold(); //获取接收缓冲区产生OnComm事件的阈值
若该值设为0(默认值),则不论接收缓冲区中有多少个字符,都不会产生OnComm事件。
6.CommEvent属性
当MSComm控件在运行时发生错误或者产生各种事件时,它会向应用程序报告错误或者事件的类型,该属性值表示这些错误或事件的类型。使用如下函数来操作该属性:
short GetCommEvent(); //获取当前事件的类型
void SetCommEvent(short nNewValue); //设置事件的类型(该函数存在,但并不可用)
使用GetCommEvent()函数即可获取当前事件的类型号码。编程时根据该属性值来做出相应的操作。
7.EOFEnable属性
EOFEnable属性决定在输入过程中,MSComm控件是否寻找文件结尾EOF字符。使用如下函数来操作:
void SetEOFEnable(BOOL bNewValue); //设置是否允许输入结束符
BOOL GetEOFEnable(); //查询当前是否允许输入结束符
若该属性值设为TURE,则寻找EOF字符,并且当找到EOF字符时,将停止输入并激活OmComm事件,同时将CommEvent属性设置为comEvEOF。若该属性设置为FALSE,则不在输入数据中寻找EOF字符。使用SetEOFEnable(BOOL bNewValue)函数设置该属性值,使用GetEOFEnable()函数可以获取当前的设置。
四:与输出操作有关的属性
以下属性都与输出操作有关。当对输出及输出缓冲区进行特定的操作时,通常要配置这些属性。
1.OutBufferSize属性
该属性值指示输出缓冲区的长度,以字节为单位。使用如下函数来操作该属性:
void SetOutBufferSize(short nNewValue); //设置输出缓冲区的长度
short GetOutBufferSize(); //获取当前输出缓冲区的长度
使用SetOutBufferSize(short nNewValue)函数设置该属性值,使用GetOutBufferSize()函数可以获取该属性的当前设定值。
2.OutBufferCount属性
该属性值反映当前输出缓冲区中的有效可用字符个数。使用如下函数来操作该属性:
short GetOutBufferCout(); //获取当前输出缓冲区中的字符个数
void SetOutBufferCount(short nNewValue); //设置输出缓冲区中的字符个数
3.SThreshold属性
该属性值是一个阈值。当发送缓冲区中的字符数达到该阈值时,MSComm控件将产生OnComm事件,并且CommEvent属性值被设为CommEvSend。使用如下函数来操作该属性:
void SetSThreshold(short nNewValue); //设置发送缓冲区产生OnComm事件的阈值
short GetSThreshold(); //获取发送缓冲区产生OnComm事件的阈值
如果该阈值被设为0(默认值),则发送缓冲区内容的变化不会产生发送事件。使用SetSThreshold(short nNewValue)函数设置该属性值,使用GetSThreshold()函数则可以获取当前属性值。
五:MSComm控件的事件
该控件只有一个事件,即OnComm事件。无论何时当CommEvent属性的值发送变化时,就会激发OnComm事件,这表明控件新发生了一个通信事件或一个错误。根据CommEvent属性可以判断出具体发生了什么事件。通常在OnComm事件的响应代码段内使用Switch函数根据CommEvent值来分别执行各种情况下的处理程序。
六:对不同类型数据的处理方法
使用MSComm控件时,其中一个难点是对输入缓冲区和输出缓冲区的数据进行处理。因为向缓冲区写入的数据及从输入缓冲区读出的数据都是VARIANT类型的数据,而程序中常用的通信数据既可能是文本型的字符串,又可能是二进制的数值。能否处理好字符串与VARIANT类型数据间的转换及二进制数据与VARIANT类型数据的转换,对能否成功应用串口通信至关重要。下面针对一般的应用情况,给出使用的处理方法。
1.使用MSComm控件发送与接收字符串
当通信传输的数据都是纯文本型的数据时,可以使用以下方法来处理接收数据与发送数据。
1)接收字符串(使用VARIANT结构)
当已经确定了接收缓冲区中含有有效字符,需要从接收缓冲区中取出字符,此时的操作代码如下:
VARIANT input1; //(1)定义一个VARIANT结构的变量
char *str, *str1;
int counts, i;
CString input2;
counts = myComm.GetInBufferCount(); //(2)获取当前输入缓冲区中待读取数据的个数
if (counts > 0)
{
intput1 = myComm.GetInput(); //(3)将接收缓冲区内容读至input1中
str = (char*)(unsigned char*)input1.parray->pvData; //(4)将input1变量的数据指针赋值给字符指针
}
i = 0;
str1 = str;
while (i < counts)
{
i++;
str1++;
}
*str1 = '\0';
input2 = (counts char*)str; //(5)根据counts值,得到有效的输入input2
上面的程序中,myComm是CMSComm控件类的实例。注释(4)语句是这段程序的关键点。VARIANT其实是一种结构,它包含有一个SAFEARRAY类型的指针成员parray。而SAFEARRAY也是一个结构,它包含有一个HUGEP类型的指针成员pvData,这个pvData就指向input1变量存放数据的地址。这样,将该地址赋值给字符指针str,便可以得到输入缓冲区中的字符串内容了。
2)发送字符串(使用COleVariant类)
在程序任何地方都可以执行发送操作。
CString SendData1;
SendData1 = "atz";
myComm.SetOutput(COleVariant(SendData1));
这段程序的关键是第三行,使用COleVariant(CString & strSrc)将字符串转换为VARIANT类型变量。
2.使用MSComm控件发送和接收二进制数据
通信传输的数据除纯文本型的数据外,还包括无法显示的控制字符(如 02)。此时就必须按照二进制数据的处理方法,对发送数据和接收数据进行处理。
1)接收二进制数据(使用VARIANT结构和COleSafeArray类)
已经确定了接收缓冲区中含有有效二进制的数据,需要从接收缓冲区中取出数据,此时的操作代码如下:
VARIANT input1; //(1)定义VARIANT类型变量
BYTE rxdata[2048], aa1; //(2)定义存放二进制数据的数组
long len1, k;
COleSafeArray safearray1; //(3)定义COleSafeArray类的实例
input1 = myComm.GetInput();
safearray1 = input1; //(4)将VARIANT变量赋值COleSafeArray类的实例
len1 = safearray1.GetOneDimSize(); //(5)使用COleSafeArray类的成员函数获取数据长度
for (k=0; k<len1; k++)
safearray1.GetElement(&k, rxdata+k); //(6)使用COleSafeArray类的成员函数将数据写入数组
这段程序的关键是掌握COleSafeArray类的使用方法。该类可以直接接受VARIANT类型的变量,然后可以使用该类的成员函数来完成需要的操作了。
2)发送二进制数据(使用CByteArray类及COleVariant类)
按照如下代码来发送二进制数据:
CByteArray Array1; //创建存放二进制数据的CByteArray类的实例
Array1.RemoveAll();
Array1.SetSize(3);
Array1.SetAt(0, 12);
Array1.SetAt(1, 79);
Array1.SetAt(2, 0xe2);
myComm.SetOutput(COleVariant(Array1));
这段程序的关键是CByteArray类的使用,包括成员函数RemoveAll()、SetSize(int nNewSize, int nGrowBy = -1)及SetAt(int nIndex,CObject* newElement)的使用方法。输出到缓冲区时使用COleVariat(const CByteAarry& arrSrc)将二进制数组转换为VARIANT类型的数据。
七:MSComm错误处理方法
使用MSComm控件通信,所做的工作就是向串口发送数据和从串口接收数据。发送数据通常出现问题的情况很少,基本都能按照设计者的要求来工作,但接收数据往往会产生这样或那样的问题。例如感觉使用了正确的读取命令,但是读取的就是不符合要求,还有经常会出现帧错误。诸如此类问题的产生,原因就是对控件的工作机制不甚了解。一下内容将探讨控件关于发送与接收数据的工作机制。
1.关于发送缓冲区
发送缓冲区作为待发送串行数据的缓冲区,其相关属性的设置值与其他通信值间的关系会影响到控件的发送数据的成功与否。当在程序中要向串口发送数据时,会使用SetOutput()函数。执行SetOutput()函数后,待发送数据并未马上被串口发送出去,而是进入发送缓冲区排队。串口硬件电路根据通信的波特率,以一定的时间间隔不断从发送缓冲区获取数据,然后将其通过COM口发送出去。由于待发送数据需要经过发送缓冲区中转一下,就会产生下面的问题。
当发送数据的频率高,即SetOutput函数执行频繁,而通信波特率低时,会导致发送缓冲区中的数据不能及时发送出去,这样,发送缓冲区中的数据会越来越多。当发送缓冲区中数据长度超过InBufferSize属性值时,将会出现发送缓冲区溢出,从而导致部分待发数据丢失。此时,控件会产生一个通信错误事件。CommEevent属性值被设置为1010,代表发送缓冲区溢出错误。
为解决上述问题,可以通过提高发送缓冲区长度,即将OutBufferSize属性值设置得大一些来解决。但这只能从一定程度上解决该问题。解决该问题的根本方法是降低发送数据的频率,或者提高串口通信的波特率。
2.关于接收缓冲区
接收缓冲区大小的设置,及从接收缓冲区读取数据的方法都会影响到能否使串口按照预期的要求进行工作。在串口通信时,串口硬件电路根据COM端口获取数据,然后将数据依次放入接收缓冲区中。特别说明,硬件电路这些操作并不受软件的任何控制。MSComm控件监测接收缓冲区中的数据长度。当发现其大于RThreshold属性时,产生OnComm事件,并且将CommEvent属性值设置为2,以通知串口程序接收缓冲区中收到的数据。当串口通信没有使用硬件握手时,与计算机串口通信的外部设备始终认为计算机准备好接收数据了,因此它可能不停地向计算机发送数据。串口程序必须及时将接收缓冲区中的数据读出,否则,不断写入的新数据将使接收缓冲区溢出,导致数据丢失。
为提高程序效率,通常接收数据的操作都是在OnComm事件中进行的。简单的将RThreshold属性值设为1,即接收缓冲区中有字符就产生OnComm事件,然后就使用GetInput()函数读取数据。表面上看,这样做是可行的,可事实上,这样操作通常达不到预期的效果。假设现在某外部设备通过串口向计算机发送10个字符,当第一个字符进入接收缓冲区后,OnComm事件将产生。此时使用GetInput()函数读取接收缓冲区中的数据,读取的数据个数可能是1个,2个,........,10个。换言之,读取的个数将是不确定的,这通常不会是程序预期的效果。为了解决上述问题,必须合理设置RThreshold属性值、InputLen属性值。结合InBufferCount属性值,合理地采用读取数据的方法。
八:接收数据的实际处理方法
这里讨论问题的前提是串口采用的是半双工方式,使用一问一答式的通信协议。这是串口通信常用的通信形式。
1.每次接收的数据长度固定
这是最简单的一种情况。为方便说明,假设接收数据长度固定为20字节,设置RThreshold属性值为20,使用GetInput()函数读取数据,在执行函数前,设置InputLen属性值为0或者20。
2.每次接收的数据长度只有几种可能性
为方便说明,假设接收数据长度存在10字节、15字节和20字节3种可能性。
设置RThreshold属性值为10,即最小的可能长度值。在OnComm事件中,首先预读10个字节,然后判断这10个字节是完整的数据帧,还是长度为15字节或20字节数据帧的一部分。若是完整的数据帧,则设置InputLen为0或10,然后使用GetInput()函数读取缓冲区即可。若不是完整的数据帧,则设置InputLen为5,然后使用GetInput()函数读取缓冲区,将该数据与前10个字节数据组合为一个数据帧,再判断该数据帧是否是完整的一个数据帧。若是,则该数据帧即为要求的数据帧。否则,设置InputLen为5,然后使用GetInput()函数读取缓冲区,并将该数据与前15字节数据组合成一个数据帧。该数据帧即为要求的数据帧。
3.每次接收数据的长度不确定
这种情况处理起来最复杂。通常这样的数据都含有一些特定格式的数据头。可以通过这些数据头计算出整个数据帧的长度。假设这些特定格式的数据头长度为10个字节,按照下面的方法来操作。
设置RThreshold属性值为10,即接收缓冲区收到10个字符就产生OnComm事件。在OnComm事件中,首先设置InputLen属性值为10,使用GetInput()函数读取缓冲区,并根据读取的数据判断出整个数据帧的长度。然后读当前InBufferCount属性值,并判断该值是否为整个数据帧长度与10之差。若不是,则反复读取,直至该值为整个数据帧长度与10之差。此时设置InputLen属性值为0或InBufferCount属性值,并使用GetInput()函数读取缓冲区,将读取的数据与前10个数据组合为一个完整的数据帧。该数据帧即为要求的数据帧。
九:MSComm控件编程步骤
1.加载控件
Visual C++集成开发环境在默认情况下,不会包含MSComm控件,所以使用控件编程必须先将MSComm控件加载到Visual C++集成开发环境中。然后,根据程序是基于对话框还是单文档形式,采用不同的编写方法。
2.初始化及打开串口
初始化主要完成设置程序使用的串口编号;设置串口的通信参数,然后打开串口。
3.事件处理
详细说明怎么使用控件的事件驱动机制。发送代码与接收代码应该在哪个位置书写。
4.关闭串口
当使用完控件后,应该及时关闭串口,以释放串口资源。
5.通信协议
控件只能提供数据流的发送和接收处理,它不对数据流进行分析其含义的操作,故通信双方在通信前,必须约定好使用的通信协