摘 要: 本文介绍了在Win32环境下用Visual C++的MFC实现串行通信方法,用类实现多线程编程,较好地将32位串口通信的API函数封装在一个类中实现串行通信,并给出示例程序。
关键词:串行通信 多线程 Visual C++ MFC
随着计算机应用深入,经常需要通过计算机RS-232串口与外部设备通信,采集如温度、压力、重量等模拟数据,或发出控制信息,用Visual C++编制串行通信程序可有三种方法:1、采用Microsoft Win32应用程序编程接口(API)所提供串行通信函数,用SDK思路编写。2、用ActiveX通讯控件开发串行通信程序。3、用C++的MFC思路,将Win32串口通信的API函数封装在一个类中实现串行通信。前两种方法己有不少刊物已作过介绍,方法各有利弊,而第三种方法较为繁琐,不仅要了解Win32位串行通信的API函数,还要掌握多线程编程,但控制灵活,既涉及到底层编程、纠错能力强,又有C++风格,为专业C++开发人员所采用。本文就在Win32环境下串行通信、多线程编程概念作简单叙述,并给出相应的示例程序,以供参考。
一、串口通信
1.串口通信步骤 :
一般编制串行通信程序分四个部分:
A 打开串行端口:打开通信资源,设置通信参数、设置通信事件、创建读、写事件、进入等待串口消息循环。
B 读取串行端口信息,当串口发生EV_RXCHAR(接收到字符并放入了输入缓冲区)消息后读取串口、数据传输错误处理、字符串处理如回车符、空格并相应转化成数据,如果模拟量还要进行数据检验等功能。
C 写串行端口信息:将要发送的信息写入串口,相应进行错误处理。
D 断开串行端口连接:关闭事件,清除通信事件,丢弃通信资源并关闭。
2. 串口通信函数
在Win32环境下,由于Windows禁止应用程序直接和硬件打交道,所以程序员只能用Win32 API提供的串行通信函数与串行端口打交道,主要函数有:
打开、关闭通信资源 CreateFile();CloseHandle( );
设置通信资源SetCommState(()
等待串口事件WaitCommEvent()
创建、关闭事件对象 CreateEvent();CloseHandle()
串口读写ReadFile(),WriteFile()
以上函数具体如何使用见示例或联机帮助。
在Windows3.1-16位通信函数有一个WM_COMMNOTIFY消息,每当发生一个串行端口事件,通信设备驱动器就发送此消息,以便程序读、写串信端口,在Win32中已被取消,而串行端口事件(特别接收串口数据)与外部设备有关,计算机要保证及时接收数据又不使主程序暂停,就要引入多线程编程。
3. 多线程
现实生活中,许多事情都时同时进行的,在我们设计应用程序时,也就常常需要采用并行编程机制-多线程,在本示例中,主线程负责创建子线程、向串口发送信息,子线程等待串口EV_RXCHAR(收到字符放入缓冲区)事件,读取缓冲区字符并显示。一般MFC将线程分两类:用户界面线程和工作者线程,工作者线程没有消息循环,只是一般函数,本示例中采用是用户界面线程,其实现方法为:
4.从CWinThread派生新的子类。必须用宏DECLARE_DYNCREATE()和IMPLEMENT_DYNCREATE声明和实现CwinThread
5. 在CwinThread派生类覆盖以下函数。
. InitInstance:执行线程实例的初始化。
. ExitInstance:在线程终止时执行清理工作。
. Run:控制线程的函数,包含消息循环。
.OnIdle:执行线程的空闭处理。
a)调用全局函数AfxBeginThread启动用户界面线程。此时,采用以下原型:
CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0, DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
其中,参数pThreadClass为从CWinThread派生类对象的RUNTIME_CLASS宏调用;nPriority用于指定线程优先级(可选)可以调用API函数SetThreadPriority设置优先级;nStackSize用于指定线程的堆栈大小;dwCreateFlags为控制线程创建的附加标记,如果为CREATE_SUSPENDED,则创建线程为挂起状态,必须调用成员函数ResumeThread恢复;lpSecurityAttrs用于指定安全属性。
要保证线程之间数据正常传输,还要涉及线程间通信、线程间同步等内容,有兴趣读者可参阅有关资料。
6.
串行通信程序流程
左边是主线程,右边为子线程。实线框为视类函数,虚线为通信类函数
7. 示例程序编制
a)建工程文件ComTest
打开VisualC++5.0(6.0),利用MFC AppWizard新建ComTest工程文件,选中单文档,其余为缺省值。并在视类建立“串口测试”下拉菜单项,包括“打开串口”、“关闭串口”和“写串口测试”三项菜单。
b)建立串口通讯类
选择“Insert”菜单项中“New Class”弹出“建新类”对话框,输入类名为“CcomCUnic”
选中CwinThread为基类,并相应编写如下函数
//线程循环主函数,当线程被启动执行
int CcomCUnic::Run()
{
DWORD dwEvtMask ;
int nLength ;
BYTE abIn[ 80] ;
//创建事件句柄
osRead.hEvent= CreateEvent( NULL, TRUE, FALSE, NULL ) ;
if (osRead.hEvent == NULL)
{
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(nLength>0)pView->OutChar((LPSTR)abIn,nLength);
}
while ( nLength > 0 ) ;
}
}
//子线程结束
CloseHandle( osRead.hEvent ) ;
return CWinThread::Run();
}
//打开串行端口,设置端口参数,被视类“打开串口”调用
BOOL CcomCUnic::OpenConnection( )
{
BYTE bSet;
DCB dcb ;
BOOL fRetVal ;
COMMTIMEOUTS CommTimeOuts;
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) return FALSE;
//指定串口执行扩展功能
EscapeCommFunction( COMFile, SETDTR ) ;
return TRUE ;
}
//关闭串行端口
BOOL CcomCUnic::CloseConnection()
{
bCONNECTED = FALSE ;
//禁止串行端口所有事件
SetCommMask( COMFile, 0 ) ;
//清除数据终端就绪信号
EscapeCommFunction( COMFile, CLRDTR ) ;
//丢弃通信资源的输出或输入缓冲区字符并终止在通信资源上挂起的读、写操//场作
PurgeComm( COMFile, PURGE_TXABORT | PURGE_RXABORT |
PURGE_TXCLEAR | PURGE_RXCLEAR ) ;
CloseHandle( COMFile );
return TRUE;
}
//读串行端口数据-被RUN调用
int CcomCUnic::ReadCommBlock(LPSTR lpszBlock)
{
DWORD dwErrorFlags;
COMSTAT ComStat ;
DWORD dwLength;
ClearCommError( COMFile, &dwErrorFlags, &ComStat ) ;
dwLength = ComStat.cbInQue;
if (dwLength > 0)
ReadFile( COMFile, lpszBlock, dwLength, &dwLength, &osRead )
return ( dwLength ) ;
}
//写数据到串行端口-被视类“发送数据”调用
BOOL CcomCUnic::WriteTTYBlock(LPSTR lpBlock, int nLength )
{
DWORD dwLength;
WriteFile( COMFile, lpBlock,nLength, &dwLength, &osRead );
return true;
}
a) 编写视类“打开串口”、“关闭串口”和“发送数据”菜单响应函数;
//打开串口函数
void CComTestView::OnOpenCom()
{
//创建等待线程,因要设置串行端口参数
m_pCcomCUnic=(CcomCUnic*)
AfxBeginThread(RUNTIME_CLASS(CcomCUnic), THREAD_PRIORITY_NORMAL,
0, CREATE_SUSPENDED);
if(m_pCcomCUnic==NULL)m_bLineCom=FALSE;
else
{
//保存视类指针,显示读取串口的字符用
m_pCcomCUnic->pView=this;
//打开串口准备读串口
m_bLineCom=m_pCcomCUnic->OpenConnection( );
//串口初始化,读取串口数据
m_pCcomCUnic->bCONNECTED=TRUE;
//线程开始执行
m_pCcomCUnic->ResumeThread();
//发送串口通信DTR信号
//EscapeCommFunction(m_pCcomCUnic->COMFile, SETDTR ) ;
}
}
//关闭串口函数
void CComTestView::OnCloseCom()
{
DWORD dwStatus;
VERIFY(::GetExitCodeThread(m_pCcomCUnic->m_hThread, &dwStatus));
m_pCcomCUnic->CloseConnection();
}
//输出字符“Test OK”到串口
void CComTestView::OnWriteCom()
{
if(m_pCcomCUnic>0)
m_pCcomCUnic->WriteTTYBlock("Test OK",7);
}
//显示读取串口的字符
void CComTestView::OutChar(LPSTR abIn,int iLength)
{
CClientDC dc(this);
dc.TextOut(10,20,abIn,iLength);
}
以上示例输入完后,编译就可运行该程序,可直接与有RS232串口的设备直接相连,并注意与本示例串口设置要一致,也可直接将计算机COM1串口2、3(数据发送、数据接收)两脚相连,执行“打开串口”后,就可读取串口数据,再执行“发送数据”,这时屏幕显示“Test OK”,最后执行“关闭串口”( 注意:“打开串口”与“关闭串口”应为互斥,先执行“打开串口”,才可以执行“关闭串口”)。
本示例仅仅简单说明用MFC类编制串行通信程序,在实际编制时还有增加以下功能1、多线程同步,因子线程读取串行端口数据存放的变量,要被主线程使用,多个线程访问同一内存变量数据,会造成数据存、取错误,解决办法可用互斥信号灯,在MFC类库中,Cmutex封装了互斥信号灯对象,可用此类解决。2、实时错误处理:当计算机实时数据通信时,由于干扰等原因,数据传输可能发生错误时,程序要及时检测错误并清除,才能保证数据正常传输。3、串口参数设置,如COM1~COM4, 波特率、停止位、奇、偶校验;停止位等参数,对此感兴趣者,可与本人联系。
|