首页 > 范文大全 > 正文

基于自定义多线程串口类的串行通信

开篇:润墨网以专业的文秘视角,为您筛选了一篇基于自定义多线程串口类的串行通信范文,如需获取更多写作素材,在线客服老师一对一协助。欢迎您的阅读与分享!

【摘要】本文介绍了在VC++环境下,通过自定义串行通信类SerialPort更加灵活的实现监控系统中上位机和下位机之间的数据传输。

【关键词】串行通信类,VC++,API函数,监控系统

串行通信作为一种灵活方便可靠的通信方式,在工业控制领域仍不失为有效的通信手段。工业控制领域(如DCS系统),经常涉及到串行通信问题。为了实现微机和单片机之间的数据交换,人们用各种不同方法实现串行通信。Windows 平台已成为当今工控管理系统的主流平台,在工业领域得到广泛应用,因此开发Windows 环境下串行通信技术就显得日益重要。应用VC++开发串行通信中,自己定义串行通信类具有最好的灵活性,只要理解这种类的几个成员函数,就能方便的使用。

1、串行通信类SerialPort

Windows 9x系统提供的开放式通用功能增强接口Win32 API(应用编程接口)是一个复杂函数、消息的集合。Windows 9x下把对串口和其它通信设备的支持与基本输入输出驱动程序集成为一体,串口的打开、关闭、读取和写入所用的函数与操作文件的函数相同,系统通过被称为设备控制块DCB的数据结构对串行口和串口通信驱动程序进行配置。Win32串行通信编程的基本步骤如下:首先,打开与外设相连的串口,并设置接收和发送的缓冲区大小;然后对串口进行初始化,也就是跟据通信协议(包括通信信号的起始终止,数据的长度和格式等)对通信控制模块(BCD结构)进行设置;接下来进行读写操作,并为了数据的正确传输而对通信错误进行检查和必要的处理;最后在串行通信结束后,关闭所有串口。

为了简化使用串行通信,对用于串行通信的Windows API函数进行封装,构成串行通信类CSerial。CSerial.h中程序如下:

class CSerial

{

public:

CSerial(); // 类构造函数,不带参数,负责初始化所有类成员变量

~CSerial(); // 类析构函数

BOOL Open( int nPort = 2, int nBaud = 9600 ); // Open这个成员函数打开通信端口。带两个参数,第一个是埠号,有效值是1到4,第二个参数是波特率,返回一个布尔量

BOOL Close( void ); // Close函数关闭通信端口。类析构函数调用这个函数,所以可不用显式调用这个函数

int ReadData( void *, int ); //函数从端口接收缓冲区读入数据。第一个参数是void*缓冲区指针,资料将被放入该缓冲区;第二个参数是个整数值,给出缓冲区的大小

int SendData( const char *, int ); //函数把数据从一个缓冲区写到串行端口。它所带的第一个参数是缓冲区指针,其中包含要被发送的资料;这个函数返回已写到端口的实际字节数。

int ReadDataWaiting( void ); 函数返回等待在通信端口缓冲区中的数据,不带参数

BOOL IsOpened( void ) { return( m_bOpened ); } // 判断串口是否打开

protected:

BOOL WriteCommByte( unsigned char ); // 发送字节

HANDLE m_hIDComDev; // 通信设备句柄

OVERLAPPED m_OverlappedRead, m_OverlappedWrite; // 重叠操作结构体

BOOL m_bOpened; // 串口打开标志

HANDLE hPostEvent; // 协调线程的事件对象

};

实现文件Serial.cpp 中部分函数源代码:

1、初始化所有类成员变量

CSerial::CSerial()

{

memset( &m_OverlappedRead, 0, sizeof( OVERLAPPED ) );

memset( &m_OverlappedWrite, 0, sizeof( OVERLAPPED ) );

m_hIDComDev = NULL;

m_bOpened = FALSE;

}

2、串口初始化函数

BOOL CSerial::Open( int nPort, int nBaud )

{

if( m_bOpened ) return( TRUE );

char szPort[15];

char szComParams[50];

DCB dcb;

wsprintf( szPort, "COM%d", nPort );

m_hIDComDev = CreateFile( szPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL ); // 打开串口

if( m_hIDComDev == NULL ) return( FALSE );

memset( &m_OverlappedRead, 0, sizeof( OVERLAPPED ) );

memset( &m_OverlappedWrite, 0, sizeof( OVERLAPPED ) );

COMMTIMEOUTS CommTimeOuts;

…… 对串口进行超时设置

SetCommTimeouts( m_hIDComDev, &CommTimeOuts );

……

m_bOpened = TRUE;

return( m_bOpened );

}

3、数据发送函数

int CSerial::SendData( const char *buffer, int size )

{

if( !m_bOpened || m_hIDComDev == NULL ) return( 0 );

DWORD dwBytesWritten = 0;

int i;

for( i=0; i

WriteCommByte( buffer[i] ); // 发送每个字节

dwBytesWritten++;

}

return( (int) dwBytesWritten );

}

4、接收数据函数

int CSerial::ReadData( void *buffer, int limit )

{if( !m_bOpened || m_hIDComDev == NULL ) return( 0 );

BOOL bReadStatus;

DWORD dwBytesRead, dwErrorFlags;

COMSTAT ComStat;

ClearCommError( m_hIDComDev, &dwErrorFlags, &ComStat );

if( !ComStat.cbInQue ) return( 0 );

dwBytesRead = (DWORD) ComStat.cbInQue;

if( limit < (int) dwBytesRead ) dwBytesRead = (DWORD) limit;

bReadStatus = ReadFile( m_hIDComDev, buffer, dwBytesRead, &dwBytesRead, &m_OverlappedRead );

if( !bReadStatus ){

if( GetLastError() == ERROR_IO_PENDING ){

WaitForSingleObject( m_OverlappedRead.hEvent, 2000 );

return( (int) dwBytesRead );

}

return( 0 );

}

return( (int) dwBytesRead );

}

2、串行通信实例应用

在本实例中串行通信完成监控系统中上位机和下位机之间的数据传输。下位机负责数据采集,主要监测信号有环境温度,环境湿度,除尘器内温度,风机状态,报警信号等。PC机主要完成监视、控制其下面的单片机系统,进行集中管理。本例中采用RS-485通信接口,系统为主从式网络,PC机为上位机。具体的通信协议为:(1)下位机定时向上传送记录的事件;(2)应答发送,即PC机要得到最新事件记录,而传送时间未到时,PC机发送命令,下位机接收命令后,把最新记录传给上位机;(3)上位机发送其它命令如校时、启动、停止、手/自动等。在Windows9x中,把串口当作文件来操作,取消了原Windows3X下的WM_COMMNOTIFY消息,因此在事件驱动机制下,使用Visual C++ 编程必须自己定义消息:

#define WM_COMM_ COMMNOTIFY WM_USER+100)

ON_MESSAGE(WM_COMM_COMMNOTIFY,ProcessCOMMNotification) //消息映射入口

ProcessCOMMNotification(WPARAM wParam,LPARAM lParam);//消息响应函数说明

为了实时响应串口事件,必须在主线程之外创建1个辅助监视线程。为防止各线程的共享资源访问出错,在程序中的各线程的动作应同步化。这可利用MFC提供的同步化事件对象。

下面给出上位机串口的接收数据程序,发送程序基本类似。Cserial com;

HWND hWnd;

// 通信监视现程

UINT CommWatchProc(LPVOID pParam)

{DWORD dwTransfer,dwEvtMast;

OVERLAPPED os;

memset( &m_ol, 0, sizeof(ol) );

ol.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);

// 设置当前掩码为EX_RXCHAR

if(!SetCommMask(com.m_hIDComDev, EX_RXCHAR)) return false ;

while (com. IsOpened( void )

{

dwEvtMask=0;

// 等待通信事件

if(!WaitCommEvent(com.m_hIDComDev,&dwEvtMast, &ol)

{

if (ERROR_IO_PENDING = = GetLastError( ) ) // 指示一次悬挂操作

GetOverlappedResult(com.m_hIDComDev,&ol, &dwTransfer,TURE);

}

if(dwEvtMast& EX_RXCHAR = = EX_RXCHAR) // 取定发生的串口事件

{

// 等待允许传递WM_COMMNOTIFY消息

WaitForSingleObject(com.hPostEvent,0Xffffffff);

ResetEvent(com.m_hIDComDev);

PostMessage(hWnd,WM_COMMNOTIFY,

(WPARAM) com.m_hIDComDev,NULL);

}

}

return true;

}

// 自定义响应WM_COMMNOTIFY消息函数

LRESULT CmyView:: ProcessCOMMNotification(WPARAM wParam,

LPARAM lParam)

{

COMSTAT ComStat;

DWORD dwErrorFlags=0;

if(!com. IsOpened( void) ) return false;

ClearCommError(com.m_hIDComDev,& dwErrorFlags, & ComStat);

Char buffer[1];

int dwLength = com.ReadData(buffer, int limit )

if(dwLength >0)

{

…… // 此出进行数据处理

SetEvent(com.m_hIDComDev);

}

return 0;

}

3、应用总结

根据不同需要,选择合适的方法。我们选用的用VC++ 类实现的上位机和下位机的串行通信方法具有使用简单、编写程序方便的特点。实践证明,利用自定义串行通信类SerialPort对于近距离的RS232接口通信和远距离的RS485接口通信都能取得良好的效果,能够高速、稳定的完成实时数据采集的任务。

参考文献:

[1]李晓磊、刘长有. “利用Windows API函数实现串行口通信的方法”. 《计算机系统应用》. 1998年第10期