多线程串口编程工具CserialPort类(附VC基于MFC单文档协议通讯源程序及详细编程步骤)

时间:2021-10-10 12:34:50

老有人觉得MSComm通讯控件很土,更有人大声疾呼:忘了它吧。确实当我们对串口编程有了一定的了解后,应该用API函数写一个属于自己的串口程序,由于编程者对程序了解,对程序修改自如。但我一直没有停止过用MSComm通讯控件,那么简单的东西,对付简单的任务完全可以,但当我们需要在程序中用多个串口,而且还要做很多复杂的处理,那么最好不用MSComm通讯控件,如果这时你还不愿意自己编写底层,就用这个类:CserialPort类。



这是Remon Spekreijse写的一个串口类, 地址在:

http://codeguru.earthweb.com/network/serialport.shtml



类作者Remon Spekreijse已作了一个基于对话框的同时检测4个串口示例的程序,在上面的网址和我主页的串口源码下载页也可以找到。我在这儿主要介绍如何将这个类应用到VC中基于文档的程序中。为了加深对串口数据处理的了解,我们利用这个类解决如下问题:

问题:

串口2(COM2)每隔1秒向串口1(COM1)发送的NEMA格式的报文:串头为$,串尾为*,中间为一个xxxx的整数( 比如2345,不足4位则前面以0代替代),最后是hh校验,规定hh为xxxx四个数的半BYTE校验和,最后加上回车<CR>与换行<LF>。整个数据包为$xxxx*hh<CR><LF>。

串口1收到上述报文后,校验正确后,将发来的数据显示在视窗中,并记下发来的正确帧数和错误帧数,若正确,还向串口2发送Y,串口2收到Y后将收到的Y的计数显示在视窗中。

测试方法:

将三线制串口线联接上同一台计算机的两个串口,编好程序后就可测试。如果没有两个串口的微机,自己改改程序。



好了,你可以先下载源程序: scporttest.zip(大小:49KB,VC6,WIN9X/2000,SerialPort.h SerialPort.cpp是两个类文件)



编程步骤:

◆1. 建立程序:

建立一个基于单文档的MFC应用程序SCPortTest,所有步骤保持缺省状态。

◆2. 添加类文件:

将SerialPort.h SerialPort.cpp两个类文件复制到工程文件夹中,用Project-Add to Project-Files命令将上述两个文件加入工程。并在SCPortTestView.h中将头文件SerialPort.h说明:#include "SerialPort.h"。

◆3. 人工增加串口消息响应函数:OnCommunication(WPARAM ch, LPARAM port)

首先在SCPortTestView.h中添加串口字符接收消息WM_COMM_RXCHAR(串口接收缓冲区内有一个字符)的响应函数声明:

//{{AFX_MSG(CSCPortTestView)

afx_msg LONG OnCommunication(WPARAM ch, LPARAM port);

//}}AFX_MSG

然后在SCPortTestView.cpp文件中进行WM_COMM_RXCHAR消息映射:

BEGIN_MESSAGE_MAP(CSCPortTestView, CView)

//{{AFX_MSG_MAP(CSCPortTestView)

ON_MESSAGE(WM_COMM_RXCHAR, OnCommunication)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

接着在SCPortTestView.cpp中加入函数的实现:

LONG CSCPortTestView::OnCommunication(WPARAM ch, LPARAM port)

{ ….. }

注意:由于这个串口类加入工程后,没有自动的消息映射机制,因此上述步骤均需要手工添加。

◆4 初始化串口

在视创建时初始化串口,首先利用ClassWizardr按下图生成OnInitialUpdate()函数。

多线程串口编程工具CserialPort类(附VC基于MFC单文档协议通讯源程序及详细编程步骤)

接着在SerialPort.h文件中说明我们在程序中要用到的全局变量:

保存两个串口接收数据:

char m_chChecksum; //用于COM1的校验和计算

CString m_strRXhhCOM1; //用于存放COM1接收的半BYTE校验字节hh

CString m_strRXDataCOM1; //COM1接收数据

CString m_strRXDataCOM2; //COM2接收数据

UINT m_nRXErrorCOM1; //COM1接收数据错误帧数

UINT m_nRXErrorCOM2; //COM2接收数据错误帧数

UINT m_nRXCounterCOM1; //COM1接收数据错误帧数

UINT m_nRXCounterCOM2; //COM2接收数据错误帧数CString



再在SerialPort.h文件中说明串口类对象:CSerailPort m_ComPort[2]; (public)。

因为要初始化2个串口,所以这里用了数组。

下面是初始化串口1和串口2:

void CSCPortTestView::OnInitialUpdate()

{

CView::OnInitialUpdate();

// TODO: Add your specialized code here and/or call the base class

m_chChecksum=0; //校验和置0

m_nRXErrorCOM1=0; //COM1接收数据错误帧数置0

m_nRXErrorCOM2=0; //COM2接收数据错误帧数置0

m_nRXCounterCOM1=0; //COM1接收数据错误帧数置0

m_nRXCounterCOM2=0; //COM2接收数据错误帧数置0

m_strRXhhCOM1.Empty(); //清空半BYTE校验hh存储变量

for(int i=0;i<2;i++)

{

if (m_ComPort[i].InitPort(this,i+1,9600,'N',8,1,EV_RXFLAG | EV_RXCHAR,512))

//portnr=1(2),baud=960,parity='N',databits=8,stopsbits=1,

//dwCommEvents=EV_RXCHAR|EV_RXFLAG,nBufferSize=512

{

m_ComPort[i].StartMonitoring(); //启动串口监视线程

if(i==1) SetTimer(1,1000,NULL); //设置定时器,1秒后发送数据

}

else

{

CString str;

str.Format("COM%d 没有发现,或被其它设备占用",i+1);

AfxMessageBox(str);

}

}

}



◆5 利用ClassWizard按下图生成CSCPortTestView 的时间消息WM_TIMER响应函数:

多线程串口编程工具CserialPort类(附VC基于MFC单文档协议通讯源程序及详细编程步骤)

void CSCPortTestView::OnTimer(UINT nIDEvent)

{

// TODO: Add your message handler code here and/or call default

int randdata=rand()%9000; //产生9000以内的随机数

CString strSendData;

strSendData.Format("%04d",randdata);

SendString(strSendData, 2); //串口2发送数据;

CView::OnTimer(nIDEvent);

}



上面用到的SendString()需按如下方式生成:

在ClassView中单击鼠标右键,在环境菜单中选择Add Member Function:

多线程串口编程工具CserialPort类(附VC基于MFC单文档协议通讯源程序及详细编程步骤)

void CSCPortTestView::SendString(CString &str, int Port)

{

char checksum=0,cr=CR,lf=LF;

char c1,c2;

for(int i=0;i<str.GetLength();i++)

checksum = checksum^str[i];

c2=checksum & 0x0f; c1=((checksum >> 4) & 0x0f);

if (c1 < 10) c1+= '0'; else c1 += 'A' - 10;

if (c2 < 10) c2+= '0'; else c2 += 'A' - 10;

CString str1;

str1='$'+str+"*"+c1+c2+cr+lf;

m_ComPort[Port-1].WriteToPort((LPCTSTR)str1);

}

请注意上面函数中是如何生成校验码的,要切记的是发送的校验码生成方式和对方接收的校验检测方式要一致。

◆6 在OnCommunication(WPARAM ch, LPARAM port)函数中进行数据处理

说明:WPARAM、 LPARAM 类型是多态数据类型(polymorphic data type),在WIN32中为32位,支持多种数据类型,根据需要自动适应,这样程序有很强的适应性。在此我们可以分别理解为char和 integer 类型数据。

每当串口接收缓冲区内有一个字符时,就会产生一个WM_COMM_RXCHAR消息,触发OnCommunication函数,这时我们就可以在函数中进行数据处理,所以这个消息就是整个程序的"发动机"。

下面是根据本文最初提出的问题写出的处理函数:

LONG CSCPortTestView::OnCommunication(WPARAM ch, LPARAM port)

{

static int count1=0,count2=0,count3=0;

static char c1,c2;

static int flag;

CString strCheck="";



if(port==2) //COM2接收到数据

{

CString strtemp=(char)ch;

if(strtemp=="Y")

{

m_nRXCounterCOM2++;

CString strtemp;

strtemp.Format("COM2: NO.%06d", m_nRXCounterCOM2);

CDC* pDC=GetDC(); //准备数据显示

pDC->TextOut(10,50,strtemp);//显示接收到的数据

ReleaseDC(pDC);

}

}



if(port==1) //COM1接收到数据

{

m_strRXDataCOM1 += (char)ch;

switch(ch)

{

case '$':

m_chChecksum=0; //开始计算CheckSum

flag=0;

break;

case '*':

flag=2;

c2=m_chChecksum & 0x0f; c1=((m_chChecksum >> 4) & 0x0f);

if (c1 < 10) c1+= '0'; else c1 += 'A' - 10;

if (c2 < 10) c2+= '0'; else c2 += 'A' - 10;

break;

case CR:

break;

case LF:

m_strRXDataCOM1.Empty();

break;

default:

if(flag>0)

{

m_strRXhhCOM1 += ch; //得到收到的校验值hh

if(flag==1)

{

strCheck = strCheck+c1+c2; //计算得到的校验值hh

if(strCheck!=m_strRXhhCOM1) //如果校验有错

{

m_strRXDataCOM1.Empty();

m_nRXErrorCOM1++; //串口1错误帧数加1

}

else

{

m_nRXCounterCOM1++;

if(m_strRXDataCOM1.Left(1)=="$") //接收数据的第一个字符是$吗?

{

char tbuf[6];

char *temp=(char*)((LPCTSTR)m_strRXDataCOM1);

tbuf[0]=temp[1]; tbuf[1]=temp[2];

tbuf[2]=temp[3]; tbuf[3]=temp[4];

tbuf[4]=0; //0表示字符串的结束,必要

int data=atoi(tbuf);

CString strDisplay1,strDisplay2;

strDisplay1.Format("NO. %06d: The reseived data is %04d",m_nRXCounterCOM1,data);

strDisplay2.Format("Error Counter=%04d.",m_nRXErrorCOM1);

CDC* pDC=GetDC(); //准备数据显示

//int nColor=pDC->SetTextColor(RGB(255,255,0));

pDC->TextOut(10,10,strDisplay1);//显示接收到的数据

pDC->TextOut(30,30,strDisplay2);//显示错误帧数

//pDC->SetTextColor(nColor);

ReleaseDC(pDC);

}

CString str1="Y";

m_ComPort[0].WriteToPort((LPCTSTR)str1);//发送应答信号Y

}

m_strRXhhCOM1.Empty();

}

flag--;

}

else

m_chChecksum ^= ch;

break;

}



}

return 0;

}

感谢原作者。原文没有作者签名。