用VisualC++编制串行通信程序摘要:本文介绍了在Win32环境下用VisualC++的MFC实现串行通信方法,用类实现多线程编程,较好地将32位串口通信的API函数封装在一个类中实现串行通信,并给出示例程序。关键词:串行通信多线程VisualC++MFC随着计算机应用深入,经常需要通过计算机RS-232串口与外部设备通信,采集如温度、压力、重量等模拟数据,或发出控制信息,用VisualC++编制串行通信程序可有三种方法:1、采用MicrosoftWin32应用程序编程接口(API)所提供串行通信函数,用SDK思路编写。2、用ActiveX通讯控件开发串行通信程序。3、用C++的MFC思路,将Win32串口通信的API函数封装在一个类中实现串行通信。前两种方法己有不少刊物已作过介绍,方法各有利弊,而第三种方法较为繁琐,不仅要了解Win32位串行通信的API函数,还要掌握多线程编程,但控制灵活,既涉及到底层编程、纠错能力强,又有C++风格,为专业C++开发人员所采用。本文就在Win32环境下串行通信、多线程编程概念作简单叙述,并给出相应的示例程序,以供参考。一、串口通信1.串口通信步骤:一般编制串行通信程序分四个部分:A打开串行端口:打开通信资源,设置通信参数、设置通信事件、创建读、写事件、进入等待串口消息循环。B读取串行端口信息,当串口发生EV_RXCHAR(接收到字符并放入了输入缓冲区)消息后读取串口、数据传输错误处理、字符串处理如回车符、空格并相应转化成数据,如果模拟量还要进行数据检验等功能。C写串行端口信息:将要发送的信息写入串口,相应进行错误处理。D断开串行端口连接:关闭事件,清除通信事件,丢弃通信资源并关闭。2.串口通信函数在Win32环境下,由于Windows禁止应用程序直接和硬件打交道,所以程序员只能用Win32API提供的串行通信函数与串行端口打交道,主要函数有:打开、关闭通信资源CreateFile();CloseHandle();设置通信资源SetCommState(()等待串口事件WaitCommEvent()创建、关闭事件对象CreateEvent();CloseHandle()串口读写ReadFile(),WriteFile()以上函数具体如何使用见示例或联机帮助。在Windows3.1-16位通信函数有一个WM_COMMNOTIFY消息,每当发生一个串行端口事件,通信设备驱动器就发送此消息,以便程序读、写串信端口,在Win32中已被取消,而串行端口事件(特别接收串口数据)与外部设备有关,计算机要保证及时接收数据又不使主程序暂停,就要引入多线程编程。3.多线程现实生活中,许多事情都时同时进行的,在我们设计应用程序时,也就常常需要采用并行编程机制-多线程,在本示例中,主线程负责创建子线程、向串口发送信息,子线程等待串口EV_RXCHAR(收到字符放入缓冲区)事件,读取缓冲区字符并显示。一般MFC将线程分两类:用户界面线程和工作者线程,工作者线程没有消息循环,只是一般函数,本示例中采用是用户界面线程,其实现方法为:4.从CWinThread派生新的子类。必须用宏DECLARE_DYNCREATE()和IMPLEMENT_DYNCREATE声明和实现CwinThread5.在CwinThread派生类覆盖以下函数。.InitInstance:执行线程实例的初始化。.ExitInstance:在线程终止时执行清理工作。.Run:控制线程的函数,包含消息循环。.OnIdle:执行线程的空闭处理。a)调用全局函数AfxBeginThread启动用户界面线程。此时,采用以下原型:CWinThread*AfxBeginThread(CRuntimeClass*pThreadClass,intnPriority=THREAD_PRIORITY_NORMAL,UINTnStackSize=0,DWORDdwCreateFlags=0,LPSECURITY_ATTRIBUTESlpSecurityAttrs=NULL);其中,参数pThreadClass为从CWinThread派生类对象的RUNTIME_CLASS宏调用;nPriority用于指定线程优先级(可选)可以调用API函数SetThreadPriority设置优先级;nStackSize用于指定线程的堆栈大小;dwCreateFlags为控制线程创建的附加标记,如果为CREATE_SUSPENDED,则创建线程为挂起状态,必须调用成员函数ResumeThread恢复;lpSecurityAttrs用于指定安全属性。要保证线程之间数据正常传输,还要涉及线程间通信、线程间同步等内容,有兴趣读者可参阅有关资料。6.串行通信程序流程左边是主线程,右边为子线程。实线框为视类函数,虚线为通信类函数7.示例程序编制a)建工程文件ComTest打开VisualC++5.0(6.0),利用MFCAppWizard新建ComTest工程文件,选中单文档,其余为缺省值。并在视类建立“串口测试”下拉菜单项,包括“打开串口”、“关闭串口”和“写串口测试”三项菜单。b)建立串口通讯类选择“Insert”菜单项中“NewClass”弹出“建新类”对话框,输入类名为“CcomCUnic”选中CwinThread为基类,并相应编写如下函数//线程循环主函数,当线程被启动执行intCcomCUnic::Run(){DWORDdwEvtMask;intnLength;BYTEabIn[80];//创建事件句柄osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);if(osRead.hEvent==NULL)响应菜单“打开串口”启动用户界面线程(挂起状态)打开通信资源,串口参数设置构造通信类初始化启动子线程等待串口事件显示接收的数据读串口数据EV_RXCHAR连接标记控制线程函数RUN创建读、写串口事件断开关闭事件子线程结束子线程开始运行断开线程终止(获取退出码)连接标记“断开”向串口发送空事件关闭串口响应菜单“关闭串口”响应菜单“发送数据”写数据到串口{AfxMessageBox(NULL,建立事件失败!);return(FALSE);}//设置串口“收到字符放入缓冲区”事件if(!SetCommMask(COMFile,EV_RXCHAR))return(FALSE)//bCONNECTED串口连接标记while(bCONNECTED){dwEvtMask=0;//等待串口事件WaitCommEvent(COMFile,&dwEvtMask,NULL);if((dwEvtMask&EV_RXCHAR)==EV_RXCHAR){do{//读取串口字符nLength=ReadCommBlock((LPSTR)abIn);//输出接收字符if(nLength0)pView-OutChar((LPSTR)abIn,nLength);}while(nLength0);}}//子线程结束CloseHandle(osRead.hEvent);returnCWinThread::Run();}//打开串行端口,设置端口参数,被视类“打开串口”调用BOOLCcomCUnic::OpenConnection(){BYTEbSet;DCBdcb;BOOLfRetVal;COMMTIMEOUTSCommTimeOuts;if((COMFile=CreateFile(COM1,GENERIC_READ|GENERIC_WRITE,//可读、可写0,//不共享NULL,//无安全描OPEN_EXISTING,//打开已存在文件FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//文件属性NULL))==(HANDLE)-1)return(FALSE);//指定监视事件_收到字符放入缓冲区SetCommMask(COMFile,EV_RXCHAR);//设置缓冲区SetupComm(COMFile,4096,4096);//刷清缓冲区PurgeComm(COMFile,PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);meOuts.ReadIntervalTimeout=0xFFFFFFFF;CommTimeOuts.ReadTotalTimeoutMultiplier=0;CommTimeOuts.ReadTotalTimeoutConstant=1000;CommTimeOuts.WriteTotalTimeoutMultiplier=2*CBR_9600/9600;CommTimeOuts.WriteTotalTimeoutConstant=0;//给定串口读与操作限时SetCommTimeouts(COMFile,&CommTimeOuts);//设置串口参数:波特率=9600;停止位1个;无校验;8位dcb.DCBlength=sizeof(DCB);GetCommState(COMFile,&dcb);dcb.BaudRate=CBR_9600;dcb.StopBits=ONESTOPBIT;dcb.Parity=NOPARITY;dcb.ByteSize=8;dcb.fBinary=TRUE;dcb.fOutxDsrFlow=0;dcb.fDtrControl=DTR_CONTROL_ENABLE;dcb.fOutxCtsFlow=0;dcb.fRtsControl=RTS_CONTROL_ENABLE;dcb.fInX=dcb.fOutX=1;dcb.XonChar=ASCII_XON;dcb.XoffChar=ASCII_XOFF;dcb.XonLim=100;dcb.XoffLim=100;dcb.fBinary=TRUE;dcb.fParity=TRUE;//根据设备控制块配置通信设备fRetVal=SetCommState(COMFile,&dcb);if(!fRetVal)returnFALSE;//指定串口执行扩展功能EscapeCommFunction(COMFile,SETDTR);returnTRUE;}//关闭串行端口BOOLCcomCUnic::CloseConnection(){bCONNECTED=FALSE;//禁止串行端口所有事件SetCommMask(COMFile,0);//清除数据终端就绪信号EscapeCommFunction(COMFile,CLRDTR);//丢弃通信资源的输出或输入缓冲区字符并终止在通信资源上挂起的读、写操//场作PurgeComm(COMFile,PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);CloseHandle(COMFile);returnTRUE;}//读串行端口数据-被RUN调用intCcomCUnic::ReadCommBlock(LPSTRlpszBlock){DWORDdwErrorFlags;COMSTATComStat;DWORDdwLength;ClearCommError(COMFile,&dwErrorFlags,&ComStat);dwLength=ComStat.cbInQue;if(dwLength0)ReadFile(COMFile,lpszBlock,dwLength,&dwLength,&osRead)return(dwLength);}//写数据到串行端口-被视类“发送数据”调用BOOLCcomCUnic::WriteTTYBlock(LPSTRlpBlock,intnLength){DWORDdwLength;WriteFile(COMFile,lpBlock,nLength,&dwLength,&osRead);returntrue;}a)编写视类“打开串口”、“关闭串口