老有人觉得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()函数。

接着在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响应函数:

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:

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;

}

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

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

  1. VC++ MFC单文档应用程序SDI下调用glGenBuffersARB(1, &pbo)方法编译通过但执行时出错原因分析及解决办法:glewInit()初始化的错误

    1.问题症状 在VC++环境下,利用MFC单文档应用程序SDI下开发OpenGL程序,当调用glGenBuffersARB(1, &pbo)方法编译通过但执行时出错,出错代码如下: OpenG ...

  2. 【2016.3.30项目技术记录】]VS2010自动生成MFC单文档框架程序的修改:去除属性框,在CViewTree类中添加鼠标单击响应

    转自http://blog.csdn.net/yanfeiouc2009/archive/2010/06/07/5653360.aspx 手头上有个东西要用到单文档,由于想省事,直接用VS2010做了 ...

  3. VC基于单文档OpenGL框架

    本文是在VC6.0的环境下,运用MFC实现的OpenGL最基本框架,需要简单了解MFC编程(会在VC6.0里创建MFC单文档应用程序就行),甚至不必了解OpenGL的知识.以下是具体的步骤. 1.创建 ...

  4. C++MFC编程笔记day05 文档类-单文档和多文档应用程序

    文档类 1 相关类    CDocument类-父类是CCmdTarget类,所以,文档类也能够处理菜单等               命令消息. 作用保存和管理数据.    注意事项:怎样解决断言错 ...

  5. 【VC编程技巧】窗口☞3.5对单文档或者多文档程序制作启动画面

    (一)概要: 文章描写叙述了如何通过Visual C++ 2012或者Visual C++ .NET,为单文档或者多文档程序制作启动画面.在Microsoft Visual Studio 6.0中对于 ...

  6. VS2010/MFC编程入门之二(利用MFC向导生成单文档应用程序框架)

    VS2010/MFC编程入门之二(利用MFC向导生成单文档应用程序框架)-软件开发-鸡啄米 http://www.jizhuomi.com/software/141.html   上一讲中讲了VS20 ...

  7. 用VC++MFC做文本编辑器(单文档模式)

    用VC++MFC做文本编辑器(单文档模式) 原来做过一个用对话框实现的文本编辑器,其实用MFC模板里面的单文档模板也可以做,甚至更加方便,适合入门级的爱好者试试,现介绍方法如下: < xmlna ...

  8. 第15.37节 PyQt(Python+Qt)入门学习:containers容器类部件QMdiArea多文档界面部件详解及编程开发案例

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 一.引言 老猿在前期学习PyQt相关知识时,对每个组件的属性及方法都研 ...

  9. 【C#附源码】数据库文档生成工具支持(Excel+Html)

    [2015] 很多时候,我们在生成数据库文档时,使用某些工具,可效果总不理想,不是内容不详细,就是表现效果一般般.很多还是word.html的.看着真是别扭.本人习惯用Excel,所以闲暇时,就简单的 ...

随机推荐

  1. 1.1 从UNIX到Linux的发展历程

    MIT的CTSS:第一个分时操作系统 ◼ Multics系统(Multiplexed Information and Computing System) ⚫ 1965年AT&T,MIT和GE的 ...

  2. python基础之生成器,生成器函数,列表推导式

    内容梗概: 1. 生成器和生成器函数. 2. 列表推导式. 1.生成器函数1.1 生成器函数. 就是把return换成yield def gen(): print("爽歪歪") y ...

  3. function_exists

    在已经定义的函数列表(包括系统自带的函数和用户自定义的函数)中查找 function_name. 如果 function_name 存在且的确是一个函数就返回 TRUE ,反之则返回 FALSE .

  4. Spring缓存注解

    从3.1开始,Spring引入了对Cache的支持.其使用方法和原理都类似于Spring对事务管理的支持.Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该 ...

  5. activiti部署流程定义时出错:INSERT INTO ACT_GE_BYTEARRAY,修改数据库编码

    activiti部署流程定义时出错 // 部署流程定义 Deployment deployment = deploymentBuilder.deploy(); 错误信息:(有乱码的...没留下截图.. ...

  6. (三)使用链式数据实现包(java)

    目标: 1) 描述数据的链式组织方式 2) 描述如何在链式节点链的开头添加新节点 3) 描述如何删除链式节点链的首节点 4) 描述如何在链式节点链中找到某个数据 5) 使用链式节点链实现ADT包 6) ...

  7. 利用JsonSchema校验json数据内容的合规性(转)

    原文地址:Json schema 背景: 复杂的AJAX应用程序可以与数百个不同的JSON服务进行交互,因此,引入对客户端验证的需求. 在处理校验问题方面有着很多的工具,但是通常可以将它们归为以下几类 ...

  8. 一、集合框架(HashMap和Hashtable的区别)

    一.HashMap和Hashtable 都实现了Map接口,都是以key-value形式保存数据. 区别一: HashMap可以存放null Hashtable不能存放null 区别二: HashMa ...

  9. RocketMQ消息存储

    转载:RocketMQ源码学习--消息存储篇 消息中间件—RocketMQ消息存储(一) RocketMQ高性能之底层存储设计 存储架构 RMQ存储架构 上图即为RocketMQ的消息存储整体架构,R ...

  10. python截取字符串

    str = ‘0123456789’ print str[0:3] #截取第一位到第三位的字符 print str[:] #截取字符串的全部字符 print str[6:] #截取第七个字符到结尾 p ...