首页

 

发表论文

 
自制软件
 
串口通讯
 
VC/C编程
 
网络通讯
 
机器人控制
 
MATLAB
 
   

Windows 95中的串行通信

作者:王齐

龚建伟评论:本文方法在Win95下VC2.0下完成,直接从底层编程,用CreateFile()打开串行口,且说明了线程的应用,对于想了解串口通信底层的读者可以读者读读,对于想轻轻松松地完成编程任务的朋友,就不用看了,本专栏不是还有许多让你轻松的文章吗。

 


     随着Windows 95 (以下简称Win95) 的逐步普及,程序员们越来越愿意在Win95下编程。随着Visual C++的引入,程序员们从繁琐的Windows编程中解脱出来。本人编写在Win95 下的串行通信程序时,找出了用VC++进行串行通信的方法。现做一介绍,与大家一起探讨。

一、基本原理

    Windows 3.1中的通信函数(如OpenComm, ReadComm)在Win95中(或者不如说是在Win32中)已不复存在,而且WM-COMMNOTIFY 消息也消失了。要在Win95中进行串行通信首先要掌握多线程编程。下面我们从进程开始简介多线程编程。进程就是应用程序的执行实例。而线程则是进程内部执行的路径。要明白每个进程都由单线程开始。线程是操作系统为其分配CPU时间的基本实体。每个线程维护一组在等待调度处理时保存其上下文的结构,上下文包括线程的机器寄存器、内核堆栈、线程环境块和线程在进程地址空间中的用户堆栈。由以上定义可以看出,线程是组成进程的基本单位。我个人认为多任务不仅体现在多进程的同步执行,而且在多线程的同时执行中也反映出来。附图就表明了在串行通信程序中多线程的概念。
    进程一开始先由主线程做一些必要的初始化工作,然后,主线程根据需要,在适当的时候建立通信监视线程,而通信监视线程监视通信口,当数字化仪有输入时,就向主线程发送WM-C OMMNOTIFY消息。主线程则对之进行处理。之后,若不需要WM-COMMNOTIFY消息了,则主线程终止通信监视线程。 多线程的同步执行,自然引起了对共享资源的访问冲突。就需要避免冲突,所以要用同步多线程对共享资源进行访问。同步的概念由此而引出。Win32 API 提供许多协调多线程执行的方法(即同步)。串行通信要用到等待函数和同步对象,以及重叠的I/O操作。下面逐一介绍这些术语以及程序中所使用的主要函数。


1.等待函数。
    Win32 API提供一组能使线程阻塞其自身执行的等待函数,这些函数直到由等待函数的参数指定的一组条件满足后才返回,这些参数包括暂停时间和一个或多个同步对象的句柄。例如:WaitForSingleObject(HANDLE hObject,DWORD dwTimeout)该函数检查指定对象的当前状态。若该对象没有信号,则调用线程进入有效等待状态。直到等待条件满足,该线程在有效等待状态,仅消耗很少的处理器时间。


2.同步对象。
    同步对象是它的句柄可以在等待函数中指定的协调多线程执行的对象。同步对象的状态要么是有信号的,它使等待函数返回,要么是无信号的,它禁止函数返回。


3.事件对象。
    事件对象是其状态通过使用SetEvent()或PulseEvent()函数可以设置成有信号的同步对象。我们所使用的是手工重置事件对象,它的状态保持有信号状态直到通过ResetEvent()函数显式地重置成无信号的事件对象。我们用CreateEvent()、CloseHandle( )函数建立和销毁事件对象。

4.重叠I/O。
    若不使用重叠I/O,则函数在它等待费时操作完成之前调用线程的执行可以无限期地阻塞;为重叠操作调用的函数可以立即返回 ,即使操作还没有完成, 这能使费时的I/O操作在后台中运行而调用线程自由执行别的任务。重叠操作要求用FILE-FLAG-OVERLAPP EDBI标志建立的通信设备,并且重叠函数要指定OVERLAPPED结构的指针,OVERLAPPED结构必须包含手工重置事件对象的句柄。如:WaitCommEvent(HANDLE hCommDev,LPD-WORD lpfdwEvt Mask,LPOVERLAPPED lpo) 该函数等待指定通信设备的特定事件发生, 事件由lpfdwEvtMask指定。该函数在重叠操作不能立即完成时,返回FALSE,并且随后调用的GetLastError()返回ERROR-IO-PENDING,指明了WaitCommEvent()在后台执行。在这种情况下:在WaitCommEvent()返回前,系统将OVERLAP PED结构的hEvent成员置为无信号的,然后当一个指定的事件或错误产生时,将hEvent成员置为有信号。GetOverlappedResult()函数可用来等待重叠操作的完成,即等待hEvent成员成为有信号的。
下面的函数比较简单,故简述如下。


CreateFile(LPCTSTR lpszName,DWORD fdwAccess, DWORD fdwShareMode,LPSECURITY- ATTRIBUTES lpsa,
DWORD fdwCreate, DWORD fdwAttrsAndFlags, HANDLE hTemplat
eFile)
功能:打开通信资源(类似:OpenComm )。
ReadFile(HANDLE hFile,LPVOID lpBuffer, DWORD nNumberOfBytesToRead,LPDWORDlp NumberOfBytesRead,
LPOVERLAPPED lpOverlapped)
功能:读操作(类似:ReadComm)。
CreateEvent(LPSECURITY-ATTRIBUTES lpsa, BOOL fManualReset,BOOL fInitialStat e, LPCTSTR
lpsaEventName)
功能:建立事件对象。
CloseHandle(HANDLE hObject)
功能:关闭通信资源(类似:CloseComm )。

二、应用说明
    在Windows中,我们可以在收到WM-CREATE消息时用CreateFile()打开串行口,此时,fdwS hareMode参数必须是零,打开独占访问的资源。fdwCreate参数必须指定为OPEN-EXISTING标志,hTemplateFile参数必须是NULL。用CreateEvent()建立事件对象,以及用AfxBeginThrea d()建立通信监视线程等一次性的工作。通信监视线程主要用WaitCommEvent()函数等待输入事件,并在输入事件发生时,向主线程发送自定义的WM-COMMNOTIFY消息(因为系统已经取消了WM-COMMNOTIFY消息)。要加入自定义的消息,只需要在消息映射中加入ON-MESSAGE()宏,但要注意该宏要放在AFX-MSG-MAP注释之外,而且响应WM-COMMNOTIFY消息的函数定义比较特殊,要按照程序中的示例进行声明及定义。主线程响应WM-COMMNOTIFY消息时,在处理函数中用Rea dFile()从串行口读取输入的字符。但在实际中发现:对于一次按键,处理函数可能会收到两次WM-COMMNOTIFY消息,要将两次读入的字符合在一起才能算作一次输入。所以在示例中对此进行了特殊处理。具体代码见示例程序。对于收到的WM-COMMNOTIFY消息,我们可直接处理, 也可转发到对话框,限于篇幅,此处未列出转发到对话框的代码。示例中将每次收到的字符串显示在状态条中。所以在用Appwizard生成应用程序框架时要选择状态条。在ReadFile()中:hFile参数是用FILE-FLAG-OVERLAPPED标志建立的,而且lpOverlapped参数不为NULL,则读操作在OVERLAPPED结构中指明的偏移处开始,而且ReadFile()可能在读操作完成前返回。在这种情况下,ReadFile()返回FALSE,GetLaseError()返回ERROR-IO-PENDING。调用线程在读操作进行时可继续运行。OVERLAPPED结构中的事件对象在读操作完成时被置为有信号。
在Visual C++ 2.0中,要注意如果不使用CWinThread对象方法建立线程的多线程应用程序,则不能从那个线程访问其他MFC对象,即不能用Win32 的CreateThread()建立线程,而应该用AfxBeginThread()函数。具体编程请参考示例程序。而相应的终止线程也要改变。若不用Visual C++编程,则需对示例程序进行相应的改变。
下面的参考程序应由Visual C++用Appwizard 产生,这里限于篇幅,只列出了主要的部分。该程序在IBM C350 上用Visual C++ 2.0编译通过。所用的数字化桌为DrawingBoardⅡ, 其输出格式为:XXXXX,XXXXX,C,D,A(说明:X-数字字符;C-按键号; D-回车符; A-换行符)。


三、示例程序


程序1:// 头文件
// commproc.h header file
#include <windows.h>
#define CN-EVENT 0x04
#define MAXBLOCK 80
// COMM 结构
typedef struct tagCOMM {
HANDLE idComDev; //通信资源句柄
BOOL fConnected; //是否打开了通信资源 TRUE=Yes
HANDLE hPostEvent; //传递自定义消息事件对象
HWND hPostToWnd; //接收自定义消息的窗口
OVERLAPPED osRead; //重叠读操作的事件对象
BOOL fPostToDlg; //是否转发到对话框 TRUE=Yes
}COMM,FAR *PCOMM;
// 宏定义 ( for easier readability )
#define COMDEV(x) (x->idComDev)
#define CONNECTED(x) (x->fConnected)
#define POSTEVENT(x) (x->hPostEvent)
#define RECIVEWND(x) (x->hPostToWnd)
#define READ-OS(x) (x->osRead)
#define TODLG(x) (x->fPostToDlg)
//通信监视函数声明
UINT CommWatchProc(LPVOID pParam);
程序2:// 视的头文件
// commtvw.h : interface of the CCommtestView class
#include "commproc.h"
class CCommtestView : public CScrollView
{
protected: // create from serialization only
CCommtestView();
DECLARE-DYNCREATE(CCommtestView)
// Attributes
public:
CCommtestDoc* GetDocument();
private:
PCOMM pComm;
BYTE bResult[MAXBLOCK];
CWinThread *pWatchThread;
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX-VIRTUAL(CCommtestView)
public:
virtual void OnDraw(CDC* pDC); // overridden to draw this view
protected:
virtual void OnInitialUpdate(); // called first time after construct
//}}AFX-VIRTUAL
// Implementation
public:
virtual ~CCommtestView();
#ifdef -DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
BOOL OpenCommRes(void);
BOOL SetupCommRes(void);
int ReadCommBlock(LPSTR abIn,int nMaxLength);
// Generated message map functions
protected:
//{{AFX-MSG(CCommtestView)
afx-msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
//}}AFX-MSG
//自定义消息接收函数
afx-msg LONG ProcessCOMMNotification (UINT wParam,
LONG lParam);
DECLARE-MESSAGE-MAP()};
#ifndef -DEBUG // debug version in commtvw.cpp
inline CCommtestDoc* CCommtestView::GetDocument()
{ return (CCommtestDoc*)m-pDocument; }
#endif
//////////////////////////////////////////////////////
程序3:// 视文件
// commtvw.cpp : implementation of the CCommtestV
iew class#include "stdafx.h"
#include "commtest.h"
#include "commtdoc.h"
#include "commtvw.h"
#ifdef -DEBUG
#undef THIS-FILE
static char BASED-CODE THIS-FILE[] = FILE;
#endif
//////////////////////////////////////////////////////
// CCommtestView
IMPLEMENT-DYNCREATE(CCommtestView, CScrollView)
BEGIN-MESSAGE-MAP(CCommtestView, CScrollView)
//{{AFX-MSG-MAP(CCommtestView)
ON-WM-CREATE()
//}}AFX-MSG-MAP
//自定义的消息映射
ON-MESSAGE(WM-COMMNOTIFY,ProcessCOMMNotification)
END-MESSAGE-MAP()
//////////////////////////////////////////////////////
// CCommtestView construction/destruction
CCommtestView::CCommtestView(){
//TODO:add construction code hare
}CCommtestView::~CCommtestView()
{
CONNECTED(pComm)=FALSE;
CloseHandle(POSTEVENT(pComm));
CloseHandle(READ-OS(pComm).hEvent);
// drop DTR
EscapeCommFunction(COMDEV(pComm),CLRDTR);
CloseHandle(COMDEV(pComm));
LocalFree(pComm);
}
//////////////////////////////////////////////////////
// CCommtestView drawing
void CCommtestView::OnDraw(CDC* pDC){
CCommtestDoc* pDoc = GetDocument();
ASSERT-VALID(pDoc);
}
void CCommtestView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal;
// TODO: calculate the total size of this view
sizeTotal.cx = sizeTotal.cy = 100;
SetScrollSizes(MM-TEXT, sizeTotal);
}
//////////////////////////////////////////////////////
// CCommtestView diagnostics
#ifdef -DEBUGvoid CCommtestView::AssertValid() const
{
CScrollView::AssertValid();
}
void CCommtestView::Dump(CDumpContext& dc) const
{
CScrollView::Dump(dc);
}
CCommtestDoc* CCommtestView::GetDocument()
// nondebug version is inline{
ASSERT(m-pDocument->IsKindOf(RUNTIME-CLASS(CCommtestDoc)));
return (CCommtestDoc*)m-pDocument;
}
#endif //-DEBUG
//////////////////////////////////////////////////////
// CCommtestView message handlers
// 响应WM-CREATE消息
int CCommtestView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CScrollView::OnCreate(lpCreateStruct) == -1)
return -1;
//为COMM结构分配空间
if (NULL == (pComm = (PCOMM) LocalAlloc( LPTR, sizeof(COMM))) ) ret urn -1;
// 初始化COMM 结构;
COMDEV(pComm) = 0;
CONNECTED(pComm) = FALSE;
RECIVEWND(pComm) = m-hWnd;
TODLG(pComm) = FALSE;
// 为重叠读建立事件对象
READ-OS(pComm).hEvent = CreateEvent ( NULL, TRUE, FALSE, FULL );
// 没有安全属性,手工重置事件,初始状态:无信号,没有名字
if ( READ-OS(pComm).hEvent == NULL ) {
LocalFree(pComm);
return -1;
}
// 为传递WM-COMMNOTIFY 消息建立事件对象
POSTEVENT(pComm) = CreateEvent (NULL, TRUE, FALSE, FULL );
// 没有安全属性,手工重置事件,初始状态:有信号,没有名字
if ( POSTEVENT(pComm) == NULL ) {
CloseHandle(READ-OS(pComm).hEvent);
LocalFree(pComm);
return -1;
}
//打开通信口并创建通信监视线程
if (!OpenCommRes()) return -1;
//MessageBox("事件对象及线程创建成功!","OnCreate");
return 0;
}
// 通信监视线程,注意:函数声明方式不要改变
UINT CommWatchProc(LPVOID pParam)
{
DWORD dwTransfer,dwEvtMask;
PCOMM pComm = (PCOMM)pParam;
OVERLAPPED os;
// 为WaitCommEvent的重叠操作建立事件对象
os.hEvent = CreateEvent (NULL, TRUE, FALSE, FULL );
// 没有安全属性,手工重置事件,初始状态:无信号,没有名字
if (os.hEvent == NULL) {
MessageBox(NULL, "Failed to create event for thread!", "ProcessCOMMN otification", MB-ICONEXCLAMATION | MB-OK );
return FALSE;
}
if ( !SetCommMask( COMDEV( pComm ),EV-RXCHAR ) )
return FALSE;
while ( CONNECTED(pComm) ) {
dwEvtMask = 0;
// 等待通信事件(重叠I/O)
if ( !WaitCommEvent( COMDEV( pComm ),&dwEvtMask,&os ) )
{
if ( ERROR-IO-PENDING == GetLastError() )
GetOverlappedResult( COMDEV( pComm), &os, &dwTransfer, TRUE);
}
if ( ( dwEvtMask & EV-RXCHAR ) == EV-RXCHAR ) {
WaitForSingleObject( POSTEVENT(pComm),0xFFFFFFFF );
// 等待允许传递WM-COMMNOTIFY消息
ResetEvent( POSTEVENT( pComm ) );
// 处理WM-COMMNOTIFY消息时不再发送 WM-COMMNOTIFY消息
// 发送WM-COMMNOTIFY消息
PostMessage( RECIVEWND(pComm), WM-COMMNOTIFY,(WPARAM) COMDEV( pComm),MAKELONG(CN-EVENT,0) );
}
}
// 关闭为WaitCommEvent的重叠操作建立的事件对象
CloseHandle( os.hEvent );
return TRUE;
} //end of CommWatchProc
// 响应WM-COMMNOTIFY消息
LONG CCommtestView::ProcessCOMMNotification(UINT wParam, LONG lParam)
{
BYTE abIn[MAXBLOCK+1];
int nLength=0;
static int first=TRUE;
if ( !CONNECTED(pComm) ) return FALSE;
//是否是由通信监视线程发送的?
if ( CN-EVENT & LOWORD(lParam) != CN-EVENT )
return FALSE;
nLength=ReadCommBlock((LPSTR)abIn,MAXBLOCK);
// 读取串口信息nLength返回读入的字符数
if (nLength == 0) { // 无法读取信息
SetEvent( POSTEVENT(pComm) );
first=TRUE;
return FALSE;
}
// 使读取的信息形成字符串,并在必要时截去其末尾的 '/r/n' 字符.
LPSTR pWhere;
abIn[nLength]=0;
pWhere=(LPSTR)strstr((const char *)abIn,"\r\n");
if (pWhere) *pWhere=0x00;
// 因为一次按键可能会收到两次WM-COMMNOTIFY消息
// 所以不得不进行特殊处理:合并两次收到的消息时读出的
// 字符串,并在最后一次收到消息时进行实际处理。若一次
// 按键收到一次WM-COMMNOTIFY消息,也做了处理。
if (first) {
lstrcpy( (LPTSTR)bResult, (LPCTSTR)abIn );
if (pWhere) // 一次按键收到一次WM-COMMNOTIFY消息, first=FALSE;
}
else lstrcat((LPTSTR)bResult,(LPCTSTR)abIn);
//MessageBox((LPCTSTR)bResult,"得到输入");
if (!first) {
//在状态行显示读到的字符串
CStatusBar* pStatus=(CStatusBar *)
AfxGetApp()->m-pMainWnd->GetDescendantWindow(AFX-IDW-STATUS-BAR);
if (pStatus) pStatus->SetPaneText(0,(LPCSTR)bResult);
}
// 允许发送下一个WM-COMMNOTIFY 消息
SetEvent( POSTEVENT(pComm) );
first=!first;
return 0L;
}
int CCommtestView::ReadCommBlock(LPSTR abIn,int nMaxLength)
{
BOOL fReadStat;
COMSTAT ComStat;
DWORD dwErrorFlags,dwLength;
ClearCommError( COMDEV(pComm), &dwErrorFlags, &ComStat );
if (dwErrorFlags>0) {
AfxMessageBox("读串口错!请检查参数设置.");
PurgeComm( COMDEV(pComm),PURGE-RXABORT | PURGE-RXCLEAR);
//丢弃错误的输入
return 0;
}
dwLength=min( (DWORD)nMaxLength, ComStat.cbInQue );
memset( abIn, 0, sizeof(abIn) ); // reset the array
if (dwLength>0) {
fReadStat = ReadFile( COMDEV(pComm), abIn, dwLength,&dwLength, &READ-OS (pComm));
if (!fReadStat) {
if (GetLastError() == ERROR-IO-PENDING) {
if (WaitForSingleObject(READ-OS(pComm).hEvent,1000))
dwLength=0; //为传送完成等待一秒
else { //等待重叠操作完成
GetOverlappedResult(COMDEV(pComm), &READ-OS(pComm),&dwLength,FALSE);
READ-OS(pComm).Offset += dwLength;
}
}
else dwLength=0;
}
}
return dwLength;
}
BOOL CCommtestView::OpenCommRes(void)
{
COMMTIMEOUTS CommTimeOuts;
BOOL fRetVal;
//打开COM1
if ((COMDEV(pComm)= CreateFile( "COM1", GENERIC-READ | GENERIC-WRITE , 0, NULL, OPEN-EXISTING, FILE-ATTRIBUTE-NORMAL | FILE-FLAG-OVERLAPP-ED, NULL )) == (HANDL E) -1)
return FALSE;
else {
SetCommMask( COMDEV(pComm), EV-RXCHAR);
// get any early notifications
SetupComm(COMDEV(pComm), 4096, 4096);
// setup device buffers
// set up for overlapped nonblocking I/O
CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF;
CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
CommTimeOuts.ReadTotalTimeoutConstant = 0;
CommTimeOuts.WriteTotalTimeoutMultiplier = 0;
CommTimeOuts.WriteTotalTimeoutConstant = 5000;
SetCommTimeouts( COMDEV(pComm), &CommTimeOuts );
}
fRetVal = SetupCommRes();
if (fRetVal) {
CONNECTED(pComm) = TRUE; //建立通信监视线程
pWatchThread=AfxBeginThread(CommWatchProc,
// Thread func
(LPVOID)pComm, // pParam
THREAD-PRIORITY-NORMAL, // Priority
(UINT)0, // StackSize
(DWORD)CREATE-SUSPENDED, // 挂起线程
(LPSECURITY-ATTRIBUTES)NULL );
// SecurityAttrs
if (pWatchThread == NULL) {
CONNECTED(pComm) = FALSE;
CloseHandle(COMDEV(pComm));
fRetVal=FALSE;
}
else {
pWatchThread>ResumeThread(); // 启动通信监视线程
EscapeCommFunction(COMDEV(pComm),SETDTR);
// assert DTR
fRetVal = TRUE; // successfully
}
}
else {
CONNECTED(pComm) = FALSE;
CloseHandle( COMDEV(pComm) );
}
return ( fRetVal );
}
BOOL CCommtestView::SetupCommRes(void)
{
// BYTE bSet;
DCB dcb;
GetCommState( COMDEV(pComm), &dcb ); // 得到通信资源当前设置
// 设定主要参数
dcb.BaudRate = 9600; // 波特率
dcb.ByteSize = 7; // 数据位
dcb.Parity = EVENPARITY; // 校验位
dcb.StopBits = ONESTOPBIT; // 停止位
return (SetCommState( COMDEV(pComm), &dcb ));
// 根据参数设定通信资源
}


参考文献
1 欣离,李莉等译. Microsoft Win 32 程序员参考大全 (二),1995.4
2 王国印译. Visual C++ 技术内幕,1994.11
3 陈维,黄昱等译. Visual C++ 2.0 for Win32 大全 (二),1996.4

回到页顶


 
   
 

                                             

转载本站原版内容,请注明作者,并说明来自http://www.gjwtech.com