/*----------------------------------------- * Copyright (c) 2008 Eric Wong * 本版紧供读者参考,不得用于任何商业行为 * * 文件名称: CESeries.cpp * 文件标识: * 摘要:用于封装WINCE 串口通讯 * * 当前版本: 1.0 * 作者: 汪兵 Eric Wong * 完成日期: 2008年1月17日 * * 取代版本: * 原作者: * 完成日期: ----------------------------------------*/ #include "StdAfx.h" #include "MySeries.h" //构造函数 CMyCESeries::CMyCESeries(UINT portNo, UINT baud, UINT parity, UINT databits, UINT stopbits) :m_portNo(portNo) ,m_baud(baud) ,m_parity(parity) ,m_databits(databits) ,m_stopbits(stopbits) ,m_hReadThread(NULL) ,m_hReadCloseEvent(NULL) { //m_portNo = portNo; //串口号使用串口1 CAN模块和GPS共用 //m_baud = baud; //波特率 //m_parity = parity; //奇偶校验,0-None,1-Odd,2-Even //m_databits = databits; //数据位 //m_stopbits = stopbits; //停止位 0-停止位1,1-停止位1.5,2-停止位2 //初始化内部变量 m_hComm = INVALID_HANDLE_VALUE; m_OnSeriesRead = NULL; m_hReadCloseEvent = NULL; m_bOpened = false; bCanComRead = true; m_bSyncOrAsync = true;//使用同步方式或者异步方式(重叠方式),默认值为true:异步方式 memset(&m_olWrite,0,sizeof(OVERLAPPED)); } //析构函数 CMyCESeries::~CMyCESeries() { if (m_bOpened) ClosePort(); } //串口读线程函数 DWORD CMyCESeries::ReadThreadFunc(LPVOID lparam) { CMyCESeries *ceSeries = (CMyCESeries*)lparam; BYTE * readBuf = NULL;//读取的字节 DWORD actualReadLen=0;//实际读取的字节数 DWORD willReadLen; DWORD dwReadErrors; COMSTAT cmState; //异步方式下将使用这些变量 //LPOVERLAPPED OVERLAPPED olWait; //memset(&olWaite,0,sizeof(olWaite)); memset(&olWait,0,sizeof(OVERLAPPED)); olWait.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); //同步方式,将使用该变量 DWORD evtMask; // 清空缓冲,并检查串口是否打开。 ASSERT(ceSeries->m_hComm !=INVALID_HANDLE_VALUE); //清空串口 PurgeComm(ceSeries->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR ); SetCommMask (ceSeries->m_hComm, EV_RXCHAR | EV_CTS | EV_DSR );//设置3个串口事件 //可以查看串口设置了哪些事件 //DWORD dwMask1; //GetCommMask(ceSeries->m_hComm,&dwMask1); OVERLAPPED olRead; memset(&olRead,0,sizeof(OVERLAPPED)); olRead.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); while(TRUE) { if(ceSeries->m_bSyncOrAsync)//异步方式下 { DWORD dwCommStatus = 0; BOOL bWait1=0; bWait1= WaitCommEvent(ceSeries->m_hComm,&dwCommStatus,&olWait);//如果以上设置的3个事件中任何一个发生了,这里主要是监测接收缓冲区中有数据时 //while(!bWait1);//等待WaitCommEvent返回为TRUE,再往下执行,这种方式行不通,重叠方式(异步方式)时,该函数一直返回为false, DWORD dwByte; //norains:It is only suitable for the GetOverlappedResult(),not undefined here. if(GetOverlappedResult(ceSeries->m_hComm,&olWait,&dwByte,TRUE) == FALSE)//正常清空下,会一直在该函数阻塞,直到olWaite中的hEvent事件发生了 //则该函数不阻塞了,并且返回值为True { if(GetLastError() != ERROR_IO_PENDING) { return 0x30; } //Clear the error flag DWORD dwErrors; COMSTAT comStat; memset(&comStat,0,sizeof(comStat)); ClearCommError(ceSeries->m_hComm,&dwErrors,&comStat); return 0x35; } //表示串口收到字符 if (dwCommStatus & EV_RXCHAR) { ClearCommError(ceSeries->m_hComm,&dwReadErrors,&cmState); willReadLen = cmState.cbInQue ; if (willReadLen <= 0) { continue; } //异步方式 //分配内存 readBuf = new BYTE[willReadLen+1]; ZeroMemory(readBuf,willReadLen+1); //读取串口数据 //异步方式 if(ReadFile(ceSeries->m_hComm, readBuf, willReadLen, &actualReadLen,&olRead)==FALSE)//异步操作,在读取完成以后返回TRUE { //释放内存 delete[] readBuf; readBuf = NULL; if(GetLastError() != ERROR_IO_PENDING) return 0x40; if(GetOverlappedResult(ceSeries->m_hComm,&olRead,&actualReadLen,TRUE) == FALSE) return 0x45; if(actualReadLen == 0) return 0x50; } else//缓冲区中的数据读取完时 { //如果读取的数据大于0, if (actualReadLen>0) { //触发读取回调函数 if (ceSeries->m_OnSeriesRead) { ceSeries->m_OnSeriesRead(ceSeries->m_pOwner,readBuf,actualReadLen); } } //释放内存 delete[] readBuf; readBuf = NULL; } } } else//同步方式下 { if (WaitCommEvent(ceSeries->m_hComm,&evtMask,0)) { SetCommMask (ceSeries->m_hComm, EV_RXCHAR | EV_CTS | EV_DSR ); //表示串口收到字符 if (evtMask & EV_RXCHAR) { ClearCommError(ceSeries->m_hComm,&dwReadErrors,&cmState); willReadLen = cmState.cbInQue ; if (willReadLen <= 0) { continue; } //分配内存 readBuf = new BYTE[willReadLen]; ZeroMemory(readBuf,willReadLen); //读取串口数据 ReadFile(ceSeries->m_hComm, readBuf, willReadLen, &actualReadLen,0); //如果读取的数据大于0, if (actualReadLen>0) { //触发读取回调函数 if (ceSeries->m_OnSeriesRead) { ceSeries->m_OnSeriesRead(ceSeries->m_pOwner,readBuf,actualReadLen); } } //释放内存 delete[] readBuf; readBuf = NULL; } } } //每次读线程执行到该处时,都会等待(或者叫阻塞)10milseconds,看是否关闭串口事件是否发生 //在ClosePort()函数体中调用了CloseHandle(m_hReadCloseEvent);来使得该事件有信号,我觉得可以用SetEvent(m_hReadCloseEvent)代替,该函数也是使得事件有信号 //当执行了ClosePotr()函数后,说明该事件已经发生了,则当在读线程函数中执行到此处时, //将会跳出while(true),从而退出读线程, if (WaitForSingleObject(ceSeries->m_hReadCloseEvent, 10) == WAIT_OBJECT_0)// { TRACE("线程ReadThreadFunc退出\n"); break; } } //关闭事件 CloseHandle( olRead.hEvent ); CloseHandle( olWait.hEvent ); //释放内存 if(readBuf) { delete[] readBuf; readBuf = NULL; } ExitThread(0); return 0; } //关闭读线程 void CMyCESeries::CloseReadThread() { if( m_hComm != INVALID_HANDLE_VALUE ) { // SetEvent(m_hReadCloseEvent); // CloseHandle(m_hReadCloseEvent); //WaitForSingleObject(m_hReadThread, 1000); // DWORD exitCode; // BOOL ret = GetExitCodeThread(m_hReadThread, &exitCode); //if(ret && exitCode != 0) { TRACE("强行关闭ReadThreadFunc线程\n"); TerminateThread(m_hReadThread,0); } // CloseHandle(m_hReadThread); m_hReadThread = NULL; } /* if( m_hComm != INVALID_HANDLE_VALUE ) { SetEvent(m_hReadCloseEvent); //设置所有事件无效 SetCommMask(m_hComm, 0); //清空所有将要读的数据 PurgeComm( m_hComm, PURGE_RXCLEAR ); //等待4秒,如果读线程没有退出,则强制退出 if (WaitForSingleObject(m_hReadThread,4000) == WAIT_TIMEOUT) { TerminateThread(m_hReadThread,0); } m_hReadThread = NULL; } //*/ } //函数介绍:打开串口 //入口参数:pPortOwner :使用此串口类的窗体句柄 // portNo :串口号 // baud :波特率 // parity :奇偶校验 // databits :数据位 // stopbits :停止位 //出口参数:(无) //返回值:TRUE:成功打开串口;FALSE:打开串口失败 BOOL CMyCESeries::OpenPort(void * pOwner, UINT portNo , //串口号 UINT baud , //波特率 UINT parity , //奇偶校验 UINT databits , //数据位 UINT stopbits , //停止位 bool isASync ) { m_portNo = portNo; //串口号使用串口1 CAN模块和GPS共用 m_baud = baud; //波特率 m_parity = parity; //奇偶校验,0-None,1-Odd,2-Even m_databits = databits; //数据位 m_stopbits = stopbits; //停止位 0-停止位1,1-停止位1.5,2-停止位2 m_bSyncOrAsync = isASync; DCB commParam; TCHAR szPort[15]; ASSERT(pOwner!=NULL); m_pOwner = pOwner; // 已经打开的话,直接返回 if (m_hComm != INVALID_HANDLE_VALUE) { return TRUE; } //设置串口名 //wsprintf(szPort, _T("\.\COM%d:"), portNo); wsprintf(szPort, _T("\\\\.\\COM%d"), portNo); //sprintf(szPort, "\\\\.\\COM%d", portNo); //打开串口 if(m_bSyncOrAsync)//m_bSyncOrAsync值为0,则用同步方式创建,否则以异步方式创建、 { m_hComm = CreateFile( //以异步方式创建 szPort, GENERIC_READ | GENERIC_WRITE, //允许读和写 0, //独占方式(共享模式) NULL, OPEN_EXISTING, //打开而不是创建(创建方式) FILE_FLAG_OVERLAPPED, 0 ); } else { m_hComm = CreateFile( //以同步方式创建 szPort, GENERIC_READ | GENERIC_WRITE, //允许读和写 0, //独占方式(共享模式) NULL, OPEN_EXISTING, //打开而不是创建(创建方式) 0, NULL ); } if (m_hComm == INVALID_HANDLE_VALUE) { // 无效句柄,返回。 TRACE(_T("CreateFile 返回无效句柄\n")); DWORD ErrNo = GetLastError(); return FALSE; } // 得到打开串口的当前属性参数,修改后再重新设置串口。 if (!GetCommState(m_hComm,&commParam)) { //关闭串口 CloseHandle (m_hComm); m_hComm = INVALID_HANDLE_VALUE; return FALSE; } if(m_bSyncOrAsync)//如果是异步方式,每创建一个串口对象,并且成功打开一个串口对象时后,才创建一个相应的发送事件 { //异步写串口 m_olWrite.hEvent = CreateEvent(NULL,TRUE,FALSE,_T("WriteData")); // DWORD dwError = GetLastError(); // if(dwError !=0) // { // TRACE("异步方式创建串口对象时,为该串口对象所创建的写串口事件对象失败"); // return FALSE; // } } //设置串口参数 commParam.BaudRate = baud; // 设置波特率 commParam.fBinary = TRUE; // 设置二进制模式,此处必须设置TRUE commParam.fParity = TRUE; // 支持奇偶校验 commParam.ByteSize = databits; // 数据位,范围:4-8 commParam.Parity = parity; // 校验模式 commParam.StopBits = stopbits; // 停止位 commParam.fOutxCtsFlow = FALSE; // No CTS output flow control commParam.fOutxDsrFlow = FALSE; // No DSR output flow control commParam.fDtrControl = DTR_CONTROL_ENABLE; // DTR flow control type commParam.fDsrSensitivity = FALSE; // DSR sensitivity commParam.fTXContinueOnXoff = TRUE; // XOFF continues Tx commParam.fOutX = FALSE; // No XON/XOFF out flow control commParam.fInX = FALSE; // No XON/XOFF in flow control commParam.fErrorChar = FALSE; // Disable error replacement commParam.fNull = FALSE; // Disable null stripping commParam.fRtsControl = RTS_CONTROL_ENABLE; // RTS flow control commParam.fAbortOnError = FALSE; // 当串口发生错误,并不终止串口读写 //设置串口参数 if (!SetCommState(m_hComm, &commParam)) { TRACE(_T("SetCommState error\n")); DWORD dwError = GetLastError(); //关闭串口 CloseHandle (m_hComm); m_hComm = INVALID_HANDLE_VALUE; return FALSE; } //设置串口读写时间 COMMTIMEOUTS CommTimeOuts; GetCommTimeouts (m_hComm, &CommTimeOuts); CommTimeOuts.ReadIntervalTimeout = MAXDWORD; CommTimeOuts.ReadTotalTimeoutMultiplier = 0; CommTimeOuts.ReadTotalTimeoutConstant = 0; CommTimeOuts.WriteTotalTimeoutMultiplier = 10; CommTimeOuts.WriteTotalTimeoutConstant = 1000; if(!SetCommTimeouts( m_hComm, &CommTimeOuts )) { TRACE( _T("SetCommTimeouts 返回错误\n") ); //关闭串口 CloseHandle (m_hComm); m_hComm = INVALID_HANDLE_VALUE; return FALSE; } //指定端口监测的事件集 SetCommMask (m_hComm, EV_RXCHAR); //分配串口设备缓冲区 SetupComm(m_hComm,1024,1024); //SetupComm(m_hComm,512,512); //初始化缓冲区中的信息 PurgeComm(m_hComm,PURGE_TXCLEAR|PURGE_RXCLEAR); CString strEvent; strEvent.Format(_T("Com_ReadCloseEvent%d"),portNo); m_hReadCloseEvent = CreateEvent(NULL,TRUE,FALSE,strEvent); //创建串口读数据监听线程 if( bCanComRead == true ) { m_hReadThread = CreateThread(NULL,0,ReadThreadFunc,this,0,&m_dwReadThreadID);//通过this指针将串口对象传递给读线程函数ReadThreadFunc的形参 } TRACE(_T("串口%d打开成功\n"), portNo); m_bOpened = true; return TRUE; } //函数介绍:关闭串口 //入口参数:(无) //出口参数:(无) //返回值: (无) void CMyCESeries::ClosePort() { //表示串口还没有打开 if (m_hComm == INVALID_HANDLE_VALUE) return ; //关闭事件 //TRACE("关闭串口读事件\n"); Sleep(100); //关闭读线程 CloseReadThread(); //关闭串口 TRACE("CMyCESeries关闭串口%d句柄\n", m_portNo); CloseHandle (m_hComm); //关闭事件 CloseHandle(m_olWrite.hEvent); m_hComm = INVALID_HANDLE_VALUE; m_bOpened = false; } //函数介绍:往串口写入数据 //入口参数:buf :待写入数据缓冲区 // bufLen : 待写入缓冲区长度 //出口参数:(无) //返回值:TRUE:设置成功;FALSE:设置失败 BOOL CMyCESeries::WriteSyncPort(const BYTE*buf , DWORD bufLen) { if( false == m_bSyncOrAsync )//同步方式 { DWORD dwNumBytesWritten; DWORD dwHaveNumWritten =0 ; //已经写入多少 int iInc = 0; //如果3次写入不成功,返回FALSE ASSERT(m_hComm != INVALID_HANDLE_VALUE); do { if (WriteFile (m_hComm, //串口句柄 buf+dwHaveNumWritten, //被写数据缓冲区 bufLen - dwHaveNumWritten, //被写数据缓冲区大小 &dwNumBytesWritten, //函数执行成功后,返回实际向串口写的个数 NULL)) //此处必须设置NULL { dwHaveNumWritten = dwHaveNumWritten + dwNumBytesWritten; //写入完成 if (dwHaveNumWritten == bufLen) { break; } iInc++; if (iInc >= 3) { TRACE("CMyCESeries::WriteSyncPort 同步写串口失败3次\n"); return FALSE; } Sleep(10); } else { TRACE("CMyCESeries::WriteSyncPort 同步写串口失败\n"); return FALSE; } }while (TRUE); return TRUE; } else//异步方式 { DWORD dwNumBytesWritten; DWORD dwHaveNumWritten =0 ; //已经写入多少 // OVERLAPPED olWrite; // memset(&olWrite,0,sizeof(OVERLAPPED)); // m_olWrite.hEvent = CreateEvent(NULL,TRUE,FALSE,_T("WriteData")); //异步写串口 if (WriteFile (m_hComm, //串口句柄 buf+dwHaveNumWritten, //被写数据缓冲区 bufLen - dwHaveNumWritten, //被写数据缓冲区大小 &dwNumBytesWritten, //函数执行成功后,返回实际向串口写的个数 &m_olWrite) == FALSE)//并不是表示写入失败,而是没有将所有数据写入到缓冲区中,而只是写入了部分而已 { if(GetLastError() != ERROR_IO_PENDING) { TRACE("CMyCESeries::WriteSyncPort 异步写串口0x20\n"); return 0x20; } if(GetOverlappedResult(m_hComm,&m_olWrite,&dwNumBytesWritten,TRUE) == FALSE)//这里将一直阻塞,直到将所有数据写入到缓冲区为止 { TRACE("CMyCESeries::WriteSyncPort 异步写串口0x25\n"); return 0x25; } } //else//写入成功 { dwHaveNumWritten = dwHaveNumWritten + dwNumBytesWritten; //写入完成 if (dwHaveNumWritten == bufLen) return TRUE; else { TRACE("CMyCESeries::WriteSyncPort 异步写串口%d失败\n", m_portNo); return FALSE; } } } } //函数介绍:设置串口读取、写入超时 //入口参数:CommTimeOuts : 指向COMMTIMEOUTS结构 //出口参数:(无) //返回值:TRUE:设置成功;FALSE:设置失败 BOOL CMyCESeries::SetSeriesTimeouts(COMMTIMEOUTS CommTimeOuts) { ASSERT(m_hComm != INVALID_HANDLE_VALUE); return SetCommTimeouts(m_hComm,&CommTimeOuts); } //得到串口是否打开 BOOL CMyCESeries::GetComOpened() { return m_bOpened; }