串口通讯的代码 。是别人写的 我加了些注释。

// Communication.h: interface for the CCommunication class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_COMMUNICATION_H__6CA00576_F088_11D1_89BB_8311A0F2733D__INCLUDED_)
#define AFX_COMMUNICATION_H__6CA00576_F088_11D1_89BB_8311A0F2733D__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
/*
_MSC_VER 定义编译器的版本
MS VC++ 10.0 _MSC_VER = 1600 
MS VC++ 9.0 _MSC_VER = 1500 
MS VC++ 8.0 _MSC_VER = 1400 
MS VC++ 7.1 _MSC_VER = 1310 
MS VC++ 7.0 _MSC_VER = 1300 
MS VC++ 6.0 _MSC_VER = 1200 
MS VC++ 5.0 _MSC_VER = 1100 
其中MS VC++ 10.0就是Visual C++ 2010,MS VC++ 9.0就是Visual C++ 2008,MS VC++ 8.0就是Visual C++ 2005。

在程序中加入_MSC_VER宏可以根据编译器版本让编译器选择性地编译一段程序。
例如一个版本编译器产生的lib文件可能不能被另一个版本的编译器调用,
那么在开发应用程序的时候,在该程序的lib调用库中放入多个版本编译器产生的lib文件。
在程序中加入_MSC_VER宏,编译器就能够在调用的时根据其版本自动选择可以链接的lib库版本,如下所示。 
#if _MSC_VER >= 1400 // for vc8, or vc9 
#ifdef _DEBUG 
#pragma comment( lib, "SomeLib-vc8-d.lib" ) 
#else if 
#pragma comment( lib, "SomeLib-vc8-r.lib" ) 
#endif 
#else if _MSC_VER >= 1310 // for vc71 
#ifdef _DEBUG 
#pragma comment( lib, "SomeLib-vc71-d.lib" ) 
#else if 
#pragma comment( lib, "SomeLib-vc71-r.lib" ) 
#endif 
#else if _MSC_VER >=1200 // for vc6 
#ifdef _DEBUG 
#pragma comment( lib, "SomeLib-vc6-d.lib" ) 
#else if 
#pragma comment( lib, "SomeLib-vc6-r.lib" ) 
#endif 
#endif 

*/

#define WM_RECEIVEPACKET WM_USER+100

class CCommunication  
{
public:
//	BOOL Connect;
	//发送数据函数
	int SendData(char *data,int len);

	//设定消息接收者
	void SetMessageReceiver(CWnd *pWnd);

	//初始化函数
	BOOL Initialize(char *device,DWORD BaudRate,int Bits,int DDV,int StopBit);

	//构造函数
	CCommunication();
	//析构函数
	virtual ~CCommunication();

	//关闭通讯接口
	BOOL CloseSerialPort();

	//CWnd是MFC窗口类的基类,提供了微软基础类库中所有窗口类的基本功能
	CWnd *msg_receiver;

	//OVERLAPPED是一个包含了用于异步输入输出的信息的结构体
	OVERLAPPED write_os;

	//定义一个句柄
	HANDLE hComPort;

private:

};

#endif // !defined(AFX_COMMUNICATION_H__6CA00576_F088_11D1_89BB_8311A0F2733D__INCLUDED_)
/*
第一种声明:

typedef struct _OVERLAPPED { 
  DWORD Internal; 
  DWORD InternalHigh; 
  DWORD Offset; 
  DWORD OffsetHigh; 
  HANDLE hEvent; 
  } OVERLAPPED
参数说明:

Internal: 预留给操作系统使用。
它指定一个独立于系统的状态,当GetOverlappedResult函数返回时没有设置扩展错误信息ERROR_IO_PENDING时有效。

InternalHigh: 预留给操作系统使用。它指定长度的数据转移,当GetOverlappedResult函数返回TRUE时有效。

Offset: 该文件的位置是从文件起始处的字节偏移量。调用进程设置这个成员之前调用ReadFile或WriteFile函数。
当读取或写入命名管道和通信设备时这个成员被忽略设为零。

OffsetHigh: 指定文件传送的字节偏移量的高位字。当读取或写入命名管道和通信设备时这个成员被忽略设为零。

hEvent: 在转移完成时处理一个事件设置为有信号状态。
调用进程集这个成员在调用ReadFile、 WriteFile、TransactNamedPipe、 ConnectNamedPipe函数之前。
*/

/*
第二种声明:

typedef struct _OVERLAPPED {
ULONG_PTR Internal; //操作系统保留,指出一个和系统相关的状态
ULONG_PTR InternalHigh; //指出发送或接收的数据长度
union {
struct {
DWORD Offset; //文件传送的字节偏移量的低位字
DWORD OffsetHigh; //文件传送的字节偏移量的高位字
};
PVOID Pointer; //指针,指向文件传送位置
};
HANDLE hEvent; //指定一个I/O操作完成后触发的事件
} OVERLAPPED, *LPOVERLAPPED;
*/

/*
I/O设备处理必然让主程序停下来干等I/O的完成,解决这个问题,可以使用OVERLAPPED。
OVERLAPPED I/O是WIN32的一项技术, 你可以要求操作系统为你传送数据,并且在传送完毕时通知你。
这项技术使你的程序在I/O进行过程中仍然能够继续处理事务。
事实上,操作系统内部正是以线程来I/O完成OVERLAPPED I/O。
,而不需付出什么痛苦的代价。也就是说,OVERLAPPED主要是设置异步I/O操作,
异步I/O操作是指应用程序可以在后台读或者写数据,而在前台做其他事情。
*/

  

// Communication.cpp: implementation of the CCommunication class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Communication.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
BOOL CState;//定义一个全局的变量 CState。

//构造函数  用于给全局变量CState置ON
CCommunication::CCommunication()
{
	CState=TRUE;
}
//析构函数  用于给全局变量CState置OFF
CCommunication::~CCommunication()
{
   CState=FALSE;
}


//具体的函数定义  。这个CommWatchProc 这个函数 一个线程函数, 这个函数是为了处理 串口相关事件的处理。
//在后面的代码里面,有个AfxCreateThrend的函数 是用于创建线程的,其中第一个参数 就要填CommWatchProc
UINT CommWatchProc(LPVOID lpData)
{
//Add message WM_RECEIVEPACKET handler in class caller:
//(wParam=pointer to data block   lParam=length of data block

//Add message WM_SENDSUCCESS handler in class caller
	CCommunication* com=(CCommunication*)lpData;
   DWORD       dwEvtMask ;
   OVERLAPPED  os;
   COMSTAT comstat;
   DWORD dwErrorFlag;
   DWORD dwLength;
	//		AfxMessageBox("Receiving");

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

   // create I/O event used for overlapped read

   os.hEvent = CreateEvent( NULL,    // no security
                            TRUE,    // explicit reset req
                            FALSE,   // initial event reset
                            NULL ) ; // no name
   if (os.hEvent == NULL)
   {
      MessageBox( NULL, _T("Failed to create event for thread!"),
		          _T("Communication Error!"),
                  MB_ICONEXCLAMATION | MB_OK ) ;
      return ( FALSE ) ;
   }

   if (!SetCommMask(com->hComPort, EV_RXCHAR ))
      return ( FALSE ) ;
   DWORD dwRead;
   char *buf;
   buf=new char[MAX_PATH];
   memset(buf,0,MAX_PATH);
   while (CState)
   {
		dwEvtMask = 0 ;

		WaitCommEvent(com->hComPort, &dwEvtMask, 0 );//等待串口通讯的事件的发生。
		Sleep(100);
		//memset(buf,0,MAX_PATH);检测返回的dwEvtMask,知道发生了什么样的串口事件
		if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR)
		{//EV_RXCHAR代表缓冲区 有数据过来了
			//read code
			ClearCommError(com->hComPort,&dwErrorFlag,&comstat);//清除错误
			dwLength=comstat.cbInQue;//输入缓冲区有多少数据?	
			if (dwLength)//如果大于0
			{
				
				if (ReadFile(com->hComPort,buf,dwLength,&dwRead,&os))//调用ReadFile函数读取缓冲区数据	
				{
					com->msg_receiver->PostMessage(WM_RECEIVEPACKET,(WPARAM)buf,(LPARAM)dwRead);	
					//想系统消息队列放入一个消息,通知程序的主线程,串口受到了数据。
				}
			}
		}
   }
   delete buf;
   // get rid of event handle
   CloseHandle( os.hEvent ) ;
   return( TRUE ) ;
} // end of CommWatchProc()

//初始化端口  定义端口的带宽 位数 奇偶校验 停止位之类的
BOOL CCommunication::Initialize(char * device,DWORD BaudRate,int Bits,int DDV,int StopBit)
{
//   srand( (unsigned)time( NULL ) );
   // open COMM device
	CState=TRUE;
	//将函数CreateFileA的的返回值赋值给hComPort,并且判定其等于(HANDLE) -1
   if (
	   (hComPort =
      CreateFileA( device,//设备名称  可以是COM2 COM3 之类的名字
					GENERIC_READ | GENERIC_WRITE,//允许读写
                  0,                    // exclusive access  必须是0
                  NULL,                 // no security attrs
                  OPEN_EXISTING,		//设置产生方式
                  FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // overlapped I/O  使用异步通讯
                  NULL )
				  
				  ) == (HANDLE) -1
		)
      return ( FALSE ) ;
   // get any early notifications

   SetCommMask(hComPort, EV_RXCHAR ) ;//设置事件驱动类型

   // setup device buffers

   SetupComm(hComPort, 10240, 10240 ) ;//设置输入 输出缓冲区的大小

   // purge any information in the buffer

   PurgeComm(hComPort, PURGE_TXABORT | PURGE_RXABORT |
                                   PURGE_TXCLEAR | PURGE_RXCLEAR ) ;//清干净输入和输出缓冲区

   // set up for overlapped I/O
   COMMTIMEOUTS CommTimeOuts;	//定义超时结构, 并且给结构的中的值赋值  
   CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF ;
   CommTimeOuts.ReadTotalTimeoutMultiplier = 0 ;
   CommTimeOuts.ReadTotalTimeoutConstant = 1000 ;
   CommTimeOuts.WriteTotalTimeoutMultiplier = 0 ;
   CommTimeOuts.WriteTotalTimeoutConstant = 10000 ;
   SetCommTimeouts(hComPort, &CommTimeOuts ) ;



   DCB dcb;//定义数据控制块结构 
   dcb.DCBlength=sizeof(DCB);
   dcb.BaudRate=BaudRate;//通讯波特率
   dcb.fBinary=1;
   dcb.fParity=1;
   dcb.fOutxCtsFlow=0;
   dcb.fOutxDsrFlow=0;
   dcb.fDtrControl=0;
   dcb.fDsrSensitivity=0;
   dcb.fTXContinueOnXoff=0;
   dcb.fOutX=0;
   dcb.fInX=0;
   dcb.fErrorChar=0;
   dcb.fNull=0;
   dcb.fRtsControl=0;
   dcb.fAbortOnError=0;
//   dcb.wReserved=0;
   dcb.XonLim=0;
   dcb.XoffLim=0;
   dcb.ByteSize=Bits;//数据位长度  Bits 就是八位的意思  也可以写数字8
   dcb.Parity=0;
   dcb.StopBits=ONESTOPBIT;//停止位 是几位  此处写的一位
   //dcb.StopBits=StopBit;

   if (!SetCommState(hComPort,&dcb))//数据配置完毕以后,就调用SetCommState函数 给端口配置
   return (FALSE);//如果配置不成功就返回 FALSE

   if (!AfxBeginThread(CommWatchProc,(LPVOID)this))//启动一个辅助线程,用于串口事件的处理
	   //此处需要对线程说明一下:
	   /*
	   Windows提供了两种线程,辅助线程和用户界面线程。区别在于:辅助线程
没有窗口,所以它没有自己的消息循环。但是辅助线程很容易编程,通常也
很有用。我们使用辅助线程。主要用它来监视串口状态,看有无数据到达、通
信有无错误;而主线程则可专心进行数据处理、提供友好的用户界面等重要
的工作。
辅助线程还有一个名字 就是工作线程。
	   */
   {
    CloseHandle(hComPort) ;//如果线程没有启动成功,就关闭句柄,并且返回FAlSE
    return (FALSE);
   }
   else
   {
    // assert DTR and RTS
    EscapeCommFunction(hComPort,SETDTR);
    EscapeCommFunction(hComPort,SETRTS);
   }
   memset( &write_os, 0, sizeof( OVERLAPPED )) ;
   write_os.hEvent = CreateEvent( NULL,    // no security
                                  TRUE,    // explicit reset req
                                  FALSE,   // initial event reset
                                  NULL ); // no name
   if (NULL == write_os.hEvent)
   {
      CloseHandle( write_os.hEvent ) ;
      return (FALSE) ;
   }
   return(TRUE);
}

//关闭串口的端口
BOOL CCommunication::CloseSerialPort()
{
	CState=FALSE;
	Sleep(10);
	if(!CloseHandle(hComPort)) return FALSE;
	else return TRUE;
}


void CCommunication::SetMessageReceiver(CWnd * pWnd)
{
	//Add a line in class caller:
	//??.SetMessageReceiver(this);
	msg_receiver=pWnd;
}

//函数: 用于发送数据 。需要提交的是发送数据的字符内容 和长度  
int CCommunication::SendData(char * data, int len)
{
	CString b;
	int a=1,c=1;
	unsigned long sendlen;
    a=WriteFile(hComPort, data, len, &sendlen, &write_os);//其实其内部调用的是WriteFile这个函数。
	//从本质上  系统把通讯的东西看成一个文件的读写。
	CString strText;
	strText.Format(",,'%s",data);
//	::Log(GetDirectory()+"\\Log\\Com.csv",strText.Left(31),TRUE,"Time,Receive,send");
	return sendlen;
}

/*
使用多线程技术,在工作线程(也就是所谓的辅助线程)里面监视串口,有数据到达时依靠事件驱动,
读取数据后向主线程汇报,注意发送数据的工作在主线程里面完成,因为通常来说发送数据的
内容(也就是所谓的下行数据)数量比较少,并且WaitCommEvent,ReadFile(),WriteFile()	都
使用非阻塞通信技术。依靠重叠(Overlappend)读写操作,让串口的读写工作在后台完成。
*/

/*
读写文件是每个Windows软件开发人员都需要做的工作。可见这项工作是非常重要的,
毕竟各种各样的数据都需要保存起来,以便作各种各样的分析,或者通过网络传送给别人。
像大家用BT下载的电影,在那个BT软件里,就需要不断从网络里接收到数据,
然后再把这些数据保存到文件里合适的位置,就可以生成跟发行者那里一样的文件,
这样才可以播放出来。又比如我在玩《征途》的游戏里,刚刚打开游戏时,
它就不断从服务器上下载更新的文件下来,然后保存到硬盘。WriteFile函数是用来写数据到文件,
ReadFile函数是从文件里读取数据出来。但这两个函数不但可以读取写磁盘的文件,
也可以接收和发送网络的数据,还有读写串口、USB、并口等设备的数据。在读写文件里,
首先就是先打开文件,然后判断打开是否成功。在写文件时,同时要注意磁盘的空间是否满等问题。
在读取文件时,往往需要读取不同位置的文件,比如要读取一个4G的视频文件,
就不可能完全把它读取到内存里,因此就需要对文件进行定位读取。
*/


/*
函数WriteFile和ReadFile声明如下:
WINBASEAPI
BOOL
WINAPI
WriteFile(
    __in        HANDLE hFile,//是文件句柄
    __in_bcount(nNumberOfBytesToWrite) LPCVOID lpBuffer,//是读写数据缓冲区
    __in        DWORD nNumberOfBytesToWrite,//是多少数据要写入
    __out_opt   LPDWORD lpNumberOfBytesWritten,//是已经写入多少数据
    __inout_opt LPOVERLAPPED lpOverlapped//是异步读写的结构
    );
   
WINBASEAPI
BOOL
WINAPI
ReadFile(
    __in        HANDLE hFile,//是文件句柄
    __out_bcount_part(nNumberOfBytesToRead, *lpNumberOfBytesRead) LPVOID lpBuffer,//是读写数据缓冲区
    __in        DWORD nNumberOfBytesToRead,//是多少数据要读取
    __out_opt   LPDWORD lpNumberOfBytesRead,//是已经读取多少数据
    __inout_opt LPOVERLAPPED lpOverlapped//是异步读写的结构
    );
 
hFile是文件句柄。
lpBuffer是读写数据缓冲区。
nNumberOfBytesToWrite是多少数据要写入。
lpNumberOfBytesWritten是已经写入多少数据。
nNumberOfBytesToRead是多少数据要读取。
nNumberOfBytesToRead是已经读取多少数据。
lpOverlapped是异步读写的结构。
 
 
调用函数的例子如下:
#001  //创建、写入、读取文件。
#002  //蔡军生 2007/10/21 QQ:9073204 深圳
#003  void CreateFileDemo(void)
#004  {
#005         //
#006         HANDLE hFile = ::CreateFile(_T("CreateFileDemo.txt"),     //创建文件的名称。
#007               GENERIC_WRITE|GENERIC_READ,          // 写和读文件。
#008               0,                      // 不共享读写。
#009               NULL,                   // 缺省安全属性。
#010               CREATE_ALWAYS,          // 如果文件存在,也创建。
#011               FILE_ATTRIBUTE_NORMAL, // 一般的文件。      
#012               NULL);                 // 模板文件为空。
#013 
#014         if (hFile == INVALID_HANDLE_VALUE)//根据返回文件句柄的值,判定创建是否成功。
#015         {
#016               //
#017               OutputDebugString(_T("CreateFile fail!/r/n"));
#018         }
#019 

#020         //往文件里写数据。
#021         const int BUFSIZE = 4096;//定义缓冲区的大小
#022         char chBuffer[BUFSIZE];    //定义一个缓冲区 其实就是一个字符数组     
#023         memcpy(chBuffer,"Test",4);//由src指向地址为起始地址的连续n个字节的数据复制到
                                       //以destin指向地址为起始地址的空间内。
#024         DWORD dwWritenSize = 0;
#025        BOOL bRet = ::WriteFile(hFile,chBuffer,4,&dwWritenSize,NULL);//将字符数组里面
									   //的数据写入 文件句柄对应的那块内存区域里面。
#026         if (bRet)
#027         {
#028               //
#029               OutputDebugString(_T("WriteFile 写文件成功/r/n"));
#030         }
#031 
#032         //先把写文件缓冲区的数据强制写入磁盘。
#033         FlushFileBuffers(hFile);
#034 
#035         //
#036         //从文件里读取数据。
#037         LONG lDistance = 0;
#038         DWORD dwPtr = SetFilePointer(hFile, lDistance, NULL, FILE_BEGIN);
#039         if (dwPtr == INVALID_SET_FILE_POINTER)
#040         {
#041               //获取出错码。
#042               DWORD dwError = GetLastError() ;
#043               //处理出错。           
#044         }
#045 
#046         DWORD dwReadSize = 0;
#047        bRet = ::ReadFile(hFile,chBuffer,4,&dwReadSize,NULL);
#048         if (bRet)
#049         {
#050               //
#051               OutputDebugString(_T("ReadFile 读文件成功/r/n"));
#052         }
#053         else
#054         {
#055               //获取出错码。
#056               DWORD dwError = GetLastError();
#057               //处理出错。           
#058               TCHAR chErrorBuf[1024];
#059               wsprintf(chErrorBuf,_T("GetLastError()=%d/r/n"),dwError);
#060               OutputDebugString(chErrorBuf);
#061         }
#062 
#063  }
*/


/*
在软件的需求里,把有用的数据保存起来是非常重要的功能。
比如每天的股票行情数据需要保存起来,以便生成K线图。
比如游戏客户端的LOG需要保存起,以便客户端出错时可以把LOG发送回来分析它出错的原因。
比如银行每天进行交易时,也需要把所有交易的数据保存到文件备份起来,以便进行结算。
还有在数据采集领域更是需要保存更多的数据,比如从DV里读取视频和语音数据出来,
就会生成12G的巨型文件。比如读DVD光盘里,把光盘做成虚拟光驱也有9G大小。
因此,创建文件是非常普通的功能,这个肯定是掌握,并且非常会使用的。
当然这个CreateFile函数不但可以创建文件,还可以打串口、并口、网络、USB设备等功能。
  
函数CreateFile声明如下:
WINBASEAPI
__out
HANDLE
WINAPI
CreateFileA(
    __in     LPCSTR lpFileName,
    __in     DWORD dwDesiredAccess,
    __in     DWORD dwShareMode,
    __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    __in     DWORD dwCreationDisposition,//是创建属性
    __in     DWORD dwFlagsAndAttributes,
    __in_opt HANDLE hTemplateFile
    );

WINBASEAPI
__out
HANDLE
WINAPI
CreateFileW(
    __in     LPCWSTR lpFileName,//是文件或是设备的名称
    __in     DWORD dwDesiredAccess,//是访问属性  
    __in     DWORD dwShareMode,//是共享模式
    __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,//是安全属性
    __in     DWORD dwCreationDisposition,//是创建属性
    __in     DWORD dwFlagsAndAttributes,//是文件标志和属性
    __in_opt HANDLE hTemplateFile//是文件模板
    );

#ifdef UNICODE
#define CreateFile CreateFileW
#else
#define CreateFile CreateFileA
#endif // !UNICODE
lpFileName是文件或设备的名称。
dwDesiredAccess是访问属性。
dwShareMode是共享属性。
lpSecurityAttributes是安全属性。
dwCreationDisposition是创建属性。
dwFlagsAndAttributes是文件标志和属性。
hTemplateFile是文件模板。
 
调用函数的例子如下:
#001  //创建文件。
#002  //蔡军生 2007/10/18 QQ:9073204 深圳
#003  void CreateFileDemo(void)
#004  {
#005         //
#006         HANDLE hFile = ::CreateFile(_T("CreateFileDemo.txt"),     //创建文件的名称。
#007              GENERIC_WRITE,          // 写文件。
#008              0,                      // 不共享读写。
#009              NULL,                   // 缺省安全属性。
#010              CREATE_ALWAYS,          // 如果文件存在,也创建。
#011              FILE_ATTRIBUTE_NORMAL, // 一般的文件。         
#012              NULL);                 // 模板文件为空。
#013 
#014         if (hFile == INVALID_HANDLE_VALUE)
#015         {
#016               //
#017               OutputDebugString(_T("CreateFile fail!/r/n"));
#018         }
#019  }
*/

  

posted @ 2015-08-29 10:39  wenluderen  阅读(624)  评论(0编辑  收藏  举报