上海做营销网站哪个公司好,网站外部外链建设,薛城区住房和城乡建设局网站,网站设计是什么出处#xff1a;http://blog.csdn.net/firehood_/article/details/6195558 现在的车载和PND设备都有自动校正系统时间的功能#xff0c;实现方法一般是通过GPS较时#xff08;当然对于有CMMB模块的设备也可以通过CMMB校时#xff09;。 但由于串口设备是一个独占设备http://blog.csdn.net/firehood_/article/details/6195558 现在的车载和PND设备都有自动校正系统时间的功能实现方法一般是通过GPS较时当然对于有CMMB模块的设备也可以通过CMMB校时。 但由于串口设备是一个独占设备GPS串口不能同时被导航软件和校时程序使用。如果此时导航软件正在运行GPS校时程序是无法访问GPS串口的。 在这样的情况下我们就需要创建一个非独占性质的串口设备将一个物理串口虚拟成多个串口来使用。 虚拟串口驱动通过打开物理串口并监听物理串口事件当物理串口有数据过来是将数据保存到缓存中供多个不同的虚拟串口使用。这样通过虚拟的方式实现了同一物理串口在多个程序之间的数据共享。 基于上述思想采用了如下设计。这里设定驱动的前缀为COM与物理串口驱动的前缀保持一致这样应用层可以像访问物理串口驱动一样访问虚拟串口驱动。 1. 在COM_Open()函数中打开物理串口并创建线程MonitorCommEventProc用于监听并处理物理串口数据。 2. 定义两个Buffer,分别保存两个虚拟串口的数据信息Buffer的设计采用循环队列的方式。 3. 在监听线程MonitorCommEventProc中监听物理串口事件当物理串口有数据过来时读取数据并将数据分别保存到两个Buffer里。 4. 在COM_Read()函数中实现读取Buffer中的数据。 5. 在COM_IOControl()函数中实现相应控制码接口。如IOCTL_SERIAL_SET_WAIT_MASKIOCTL_SERIAL_WAIT_ON_MASK、IOCTL_SERIAL_GET_COMMSTATUS、IOCTL_SERIAL_PURGE等。 MonitorCommEventProc监听线程参考代码 [cpp] view plain copy //we use this program to always read the physical serial data DWORD MonitorCommEventProc(LPVOID pParam) { InterlockedExchange(reinterpret_castLONG *(g_bMonitorProcRunning),TRUE); unsigned char vtBufRead[READ_BUFFER_LENGTH]; while(TRUE) { if(g_bExitMonitorProc ! FALSE) { RETAILMSG(TRUE,(TEXT(MonitorCommEventProc Stop!/r/n))); //if com is closed,we will stop to read break; } DWORD dwEvtMask 0; SetCommMask (g_hCom, g_dwWaitMask | EV_RXCHAR); // wait the physical serial event if(WaitCommEvent(g_hCom,dwEvtMask,NULL)(dwEvtMask EV_RXCHAR)) { SetCommMask (g_hCom, g_dwWaitMask | EV_RXCHAR); { COMSTAT cmState; DWORD dwReadErrors; ClearCommError(g_hCom,dwReadErrors,cmState); DWORD willReadLen cmState.cbInQue ; if (willReadLen 0) continue; DWORD dwRead 0; ReadFile(g_hCom,vtBufRead,willReadLen,dwRead,NULL); EnterCriticalSection(g_csRead1); EnterCriticalSection(g_csRead2); if(g_bComState1COM_STATUS_OPEN) { WriteBuffer(g_ucDateBuf1,READ_BUFFER_LENGTH,vtBufRead,dwRead, g_dwDateBufHead1,g_dwDateBufTail1); } if(g_bComState2COM_STATUS_OPEN) { WriteBuffer(g_ucDateBuf2,READ_BUFFER_LENGTH,vtBufRead,dwRead, g_dwDateBufHead2,g_dwDateBufTail2); } LeaveCriticalSection(g_csRead2); LeaveCriticalSection(g_csRead1); InterlockedExchange(reinterpret_castLONG *(g_dwEvtMask),dwEvtMask); PulseEvent(g_hCanReadEvent); printf(PulseEvent g_hCanReadEvent.../n); // sleep for other thread to respond to the event,very important.. Sleep(1); } } InterlockedExchange(reinterpret_castLONG *(g_bMonitorProcRunning),FALSE); return 0; } COM_IOControl()函数多数控制码的实现可以直接调用物理串口DeviceIoControl()来处理但有些控制码需要自己实现比如清空缓存不能直接调用DeviceIoControl()来清空物理串口的数据而是应该清空虚拟串口Buffer中的数据。几个重要控制码实现的参考代码 [cpp] view plain copy BOOL COM_IOControl(HANDLE dwHandle, DWORD dwIoControlCode, PBYTE pBufIn, DWORD dwBufInSize, PBYTE pBufOut, DWORD dwBufOutSize, PDWORD pBytesReturned) { switch(dwIoControlCode) { case IOCTL_SERIAL_SET_WAIT_MASK: //printf(Serial Command: SERIAL_SET_WAIT_MASK/n); if(g_uiOpenCount1) { g_dwWaitMask *reinterpret_castDWORD *(pBufIn); return DeviceIoControl(g_hCom, IOCTL_SERIAL_SET_WAIT_MASK,pBufIn, dwBufInSize,pBufOut,dwBufOutSize,pBytesReturned,NULL); } else return TRUE; case IOCTL_SERIAL_WAIT_ON_MASK: { PVSP_INFO pOpenHead (PVSP_INFO) dwHandle; // return immediately if the buffer has the available data if(*(pOpenHead-BufferHead)!*(pOpenHead-BufferTail)) return TRUE; if(dwBufOutSize sizeof(DWORD) || WaitForSingleObject(g_hCanReadEvent,INFINITE) WAIT_TIMEOUT) { *pBytesReturned 0; return FALSE; } else { InterlockedExchange(reinterpret_castLONG *(pBufOut),g_dwEvtMask); *pBytesReturned sizeof(DWORD); return TRUE; } } case IOCTL_SERIAL_PURGE: { //clean the virtual serial buffer PVSP_INFO pOpenHead (PVSP_INFO) dwHandle; *(pOpenHead-BufferHead)0; *(pOpenHead-BufferTail)0; return TRUE; } ..... } return FALSE; } Buffer(循环队列)读写操作参考代码 [cpp] view plain copy void WriteBuffer(PUCHAR targetBuffer,DWORD LongOfTarget,PUCHAR sourceBuffer, DWORD NumInSource,DWORD *BufferHead,DWORD *BufferTail) { BOOL ChangeHead FALSE; DWORD i*BufferTail; DWORD j0; if(NumInSource LongOfTarget) { memcpy(targetBuffer,sourceBuffer,LongOfTarget); *BufferHead0; *BufferTailLongOfTarget-1; return; } else { for(;jNumInSource;j) { targetBuffer[i]sourceBuffer[j]; if(iLongOfTarget) { i0; } if(i(*BufferHead)) { ChangeHeadTRUE; } } if(ChangeHeadFALSE) { *BufferTaili; return; } else { *BufferTaili; *BufferHeadi1; return; } } } DWORD ReadBuffer(PUCHAR targetBuffer,PUCHAR sourceBuffer,DWORD SizeOfTargeBuf, DWORD LongOfSourceBuf,DWORD *BufferHead,DWORD *BufferTail) { DWORD i0; DWORD j*BufferHead; BOOL IsEmptyFALSE; for(i0;iSizeOfTargeBuf;i) { if(j(*BufferTail)) { IsEmptyTRUE; break; } targetBuffer[i]sourceBuffer[j]; if(jLongOfSourceBuf) { j0; } } if(IsEmptyFALSE) { *BufferHeadj; } else { (*BufferHead)(*BufferTail)0; } return i; } 参考资料《WinCE虚拟串口驱动》http://blog.csdn.net/norains/archive/2009/03/28/4032257.aspx 作者norains 感谢 norains大侠无私的奉献。 关于虚拟串口 出处http://blog.csdn.net/lovelynn/article/details/4466215 我们有一个GPS模块连接在COM3上现在有两个应用程序都需要读取COM3的内容然而WinCE的串口为独占式的串口因此我们需要一个驱动程序将COM3虚拟成COM4和COM5来供应用程序使用。下面我来介绍一下驱动程序的设计。 首先我们要解决虚拟串口驱动加载的问题 加载方法一 在本程序中加载过程需要两个函数来完成一个是虚拟串口驱动的 COM_Init()另一个是 RegisterDevice()我们将在应用程序中使用RegisterDevice()来启动COM_Init()完成虚拟串口驱动的加载。在应用程序中加载虚拟串口驱动的代码如下 DWORD VirComNO 4; HANDLE hRes RegisterDevice (LCOM, VirComNO, LGPSCOM.dll, (DWORD) VirComNO); RegisterDevice函数的用法参见文档说明。 通过这个函数我们就会调用device.exe在系统中添加了一个名为COM4的设备GPSCOM.dll中的流接口COM_Init()会被调用。 加载方法二 当然我们也可以在系统启动时让device.exe直接加载本驱动。 下面我们来看COM_Init()的实现 HANDLE COM_Init( ULONG Identifier ) { PHW_INDEP_INFO pSerialHead NULL; // Allocate our control structure. //创建一个结构体用来记录设备信息 pSerialHead (PHW_INDEP_INFO)LocalAlloc(LPTR, sizeof(HW_INDEP_INFO)); pSerialHead-pAccessOwner NULL; ...... //add com Identifier //如果我们创建的是COM5这个设备那么把COM5的相关信息记录在pSerialHead中。 if(5Identifier) { RETAILMSG(DEBUG_COM2,(L PLATFORM fwq COM_init5 /r/n)); pSerialHead-COMNUM 5; g_pCircleBuffer5 CP_CreateCircleBuffer(8192); } //如果我们创建的是COM4这个设备那么把COM4的相关信息记录在pSerialHead中。 if(4Identifier) { RETAILMSG(DEBUG_COM2,(L PLATFORM fwq COM_init 4 /r/n)); pSerialHead-COMNUM 4; // init circlebuffer for com4 g_pCircleBuffer4 CP_CreateCircleBuffer(8192); } ...... //返回pSerialHead这个pSerialHead将会被COM_Open()所得到。 return(pSerialHead); } 通过RegisterDevice()和COM_Init()的配合我们可以看到每添加一个设备COM_Init()就会在device.exe的进程空间内分配一段空间用来存放相应设备的信息这些信息被记录在pSerialHead所指向的结构体中。 至此设备的加载过程就完成我们可以灵活的根据我们的需要在pSerialHead所指向的结构体中添加需要的变量这个结构体也可以我们自己来定义但在本程序中我直接引用了系统代码中定义好的结构体并在此结构体中添加了自己需要的变量。 第二驱动程序加载成功之后我们就可以通过应用程序来打开虚拟串口了。下面我们来完成COM_Open()函数。 HANDLE COM_Open( HANDLE pHead, // parm Handle returned by COM_Init. DWORD AccessCode, // parm access code. DWORD ShareMode // parm share mode - Not used in this driver. ) { RETAILMSG(DEBUG_COM2,(L PLATFORM fwq COM_Open /r/n)); // 系统会根据CreateFile的第一个参数把devcie.exe内存空间中的与具体设备相关的 //PHW_INDEP_INFO结构体通过pHead参数传递过来。 //比如CreateFile的第一个参数是COM4文件系统就回把我们在COM_Init()中创建好的用来存 //贮COM4设备信息的PHW_INDEP_INFO结构体地址传递过来。 PHW_INDEP_INFO pSerialHead (PHW_INDEP_INFO)pHead; PHW_OPEN_INFO pOpenHead NULL; ...... // 为pOpenHead分配空间这个空间内用来存放打开设备的一些信息比如运行时的状态等都可以存储在此空间内。 pOpenHead (PHW_OPEN_INFO)LocalAlloc(LPTR, sizeof(HW_OPEN_INFO)); RETAILMSG(DEBUG_COM,(LPLATFORM **()()()** pOpenHead%d ,pOpenHead)); if ( !pOpenHead ) { DEBUGMSG( DEBUG_COM, (TEXT( PLATFORMError allocating memory for pOpenHead, COM_Open failed/n/r))); return(NULL); } // Init the structure //我们要把在COM_Init()中和当前打开设备相关的pSerialHead的地址保存在pOpenHead中。 // 设备打开后其他流接口函数被调用时都会获得pOpenHead所指向的结构体地址。这样我们就可以在驱动中控制 //应用程序打开的设备状态了 pOpenHead-pSerialHead pSerialHead; ...... //InitializeCriticalSection((pOpenHead-CommEvents.EventCS)); EnterCriticalSection(g_csOpen); // 如果串口3已经被打开了 就不在继续打开。 if(g_uiOpenCount ! 0) { goto SET_SUCCEED_FLAG; } BOOL resFALSE; // 打开串口3 res g_SerialPort.Open(3,4800); if(res FALSE ) { RETAILMSG(DEBUG_COM,(TEXT(Failed to map 3/r/n))); goto CleanUp; } else { RETAILMSG(DEBUG_COM,(TEXT(Succeed to map to 3/r/n))); } g_hReadEvent4 CreateEvent(NULL,FALSE,FALSE,LWaitCommGPS4); SET_SUCCEED_FLAG: if(pSerialHead-COMNUM 4) { RETAILMSG(DEBUG_COM3,(L PLATFORM open com4 /r/n)); g_ComOpenFlag4 1; pSerialHead-COMOpenFlag 1; } if(pSerialHead-COMNUM 5) { RETAILMSG(DEBUG_COM3,(L PLATFORM open com5 /r/n)); g_ComOpenFlag5 1; pSerialHead-COMOpenFlag 1; } // 记录串口3被打开的次数 g_uiOpenCount ; LeaveCriticalSection(g_csOpen); / return(pOpenHead); CleanUp: LeaveCriticalSection(g_csOpen); RETAILMSG(DEBUG_COM,(L PLATFORM readqueue faild and exit COM_Open/r/n)); return(NULL); } 第三现在我们可以在应用程序中通过打开的串口来监听串口数据了应用程序会通过 WaitCommEvent函数来等待串口事件。 那么驱动程序中我们是如何知道应用程序在等待哪个串口呢应用程序调用WaitCommEvent函数实际上是掉用驱动的 COM_IOControl()函数。 下面来看流接口COM_IOControl()的设计 BOOL COM_IOControl(PHW_OPEN_INFO pOpenHead, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut){ ...... // 通过文件系统传来的pOpenHead参数获得应用程序所调用设备的结构体指针获取当前设 备信息。 PHW_INDEP_INFO pSerialHead pOpenHead-pSerialHead; switch ( dwCode ) { case IOCTL_SERIAL_WAIT_ON_MASK : // 应用程序调用WaitCommEvent()函数会进入这个分支 if(4 pSerialHead-COMNUM) { // WaitCommEvent4调用了WaitForSingleObject来等待事件 WaitCommEvent4(pOpenHead, (DWORD *)pBufOut, NULL); } if(5 pSerialHead-COMNUM) { WaitCommEvent5(pOpenHead, (DWORD *)pBufOut, NULL); } *pdwActualOut sizeof(DWORD); break; default : RETAILMSG (DEBUG_COM, (TEXT( PLATFORM Invalid ioctl %d/r/n), dwCode)); break; } ...... return TRUE; } 通过这个COM_IOControl()函数我们就能对COM4和COM5两个串口事件分别进行监听。 第四监听COM3 并将COM3的数据分别发送给COM4和COM5针对COM4和COM5我分别为其分配了一段循环缓冲区当COM3有数据时如果COM4为打开状态就将数据放入COM4的循环缓冲区中COM5同理。 这部分工作由一个线程来完成下面是COM3数据监听线程代码。 static DWORD WINAPI ThreadReadCOM(LPVOID lpParam) { RETAILMSG(DEBUG_CODE,(LThreadReadCOM/r/n)); DWORD dwCommModemStatus 0; BOOL bRet FALSE; int pdwBytesRead 0; BYTE pBuffer [1024]; _try { while(INVALID_HANDLE_VALUE ! g_hComFile) { //RETAILMSG(DEBUG_CODE,(LCOM1 Wait Comm Event/r/n)); SetCommMask(g_hComFile,EV_RXCHAR); WaitCommEvent (g_hComFile,dwCommModemStatus,0); //RETAILMSG(DEBUG_CODE,(L第%d次收到数据/r/n,count)); bRet ReadFile(g_hComFile,pBuffer,128,(LPDWORD)pdwBytesRead,0); if(1 g_ComOpenFlag4) { // 设置事件有效通过COM4收到数据 SetEvent(g_hReadEvent4); //将数据存放在专为COM4准备的循环缓冲区中 g_pCircleBuffer4-Write(g_pCircleBuffer4,pBuffer,pdwBytesRead); } if(1 g_ComOpenFlag5) { // 设置事件有效通过COM5收到数据 // 这里不添加SetEvent语句串口5依然会得到响应而且响应速度很快添加了 // SetEvent后com5的响应反而会变慢这里我猜测是实串口驱动的SetEvent事件引起 //了应用程序对com5的WaitCommEvent响应希望有了解的朋友给与指正 SetEvent(g_hReadEvent5); //将数据存放在专为COM5准备的循环缓冲区中 g_pCircleBuffer5-Write(g_pCircleBuffer5,pBuffer,pdwBytesRead); } } } __except(EXCEPTION_EXECUTE_HANDLER) { RETAILMSG(DEBUG_CODE,(LException! call Serial Thread/r/n)); } RETAILMSG(DEBUG_CODE,(Lreadcom Thread exit/r/n)); return 1; } 第五实现COM_Read()应用程序调用ReadFile后驱动中的COM_Read()会被调用下面是具体实现代码 ULONG COM_Read( HANDLE pHead, //parm [IN] HANDLE returned by COM_Open PUCHAR pTargetBuffer, //parm [IN,OUT] Pointer to valid memory. ULONG BufferLength //parm [IN] Size in bytes of pTargetBuffer. ) { PHW_OPEN_INFO pOpenHead (PHW_OPEN_INFO)pHead; PHW_INDEP_INFO pSerialHead pOpenHead-pSerialHead; ULONG BytesRead 0; ...... if(4 pSerialHead-COMNUM) { //从COM4的循环缓冲区中读取数据 g_pCircleBuffer4-Read(g_pCircleBuffer4,pTargetBuffer,BufferLength,(unsigned int*)BytesRead); return BytesRead; } if(5 pSerialHead-COMNUM) { //从COM5的循环缓冲区中读取数据 g_pCircleBuffer5-Read(g_pCircleBuffer5,pTargetBuffer,BufferLength,(unsigned int*)BytesRead); return BytesRead; } return -1; } 至此我们的虚拟串口驱动就基本完成当然目前本驱动只具备串口读功能如果要实现写功能还需要完成COM_Write的 代码。 还有一个重要的函数COM_Close()需要实现在这个函数中我们要把被关闭的串口占用的资源释放将某些状态位设为默认值等还要在所有虚拟串口都关闭后关闭实串口COM3结束COM3的监听线程在这里就不具体说明了。 红色字体部分是本驱动存在问题的地方希望有了解的朋友给与指正。