(一) 串口的参数设置及打开
对RS-232-C串行端口进行参数配置是使用串口进行通讯的必要条件。而且由于场合不同、用途、功能的不同对串口也采取不同的配置方式,为了使本程序更灵活,适应面更广,采取将所有的可能参数都预先设置在几个组合框中,可以在程序运行后随时更改设置。自定义一个设置串口参数的数据结构:
typedef struct tagCOM_CONFIG { int nPort; file://端口号,从COM1到COM4 int nBaud; file://波特率,从1200bps到57600bps(对应的宏为CBR_1200到CBR_57600) int nData; file://数据位个数,7位或是8位 int nStop; file://停止位个数,可以是1位、1.5位、2位。 int nParity;//采取的校验方式,有无校验(NOPARITY)、 file://奇校验(ODDPARITY)和偶校验(EVENPARITY)等。 }COM_CONFIG;
|
当选择好适当的参数后就可以根据设置好的端口配置情况打开通讯端口了。与以往DOS下串行通信程序不同的是,Windows操作平台下不提倡应用程序直接控制硬件(包括端口),也不让使用中断(除非打入到Ring0系统级),而是通过Windows操作系统提供的设备驱动程序来进行数据传递。在Windows操作系统下串行口和其他通讯端口一样是作为文件来进行处理的,而不是直接对端口进行操作,对于串行通信,Win 32 提供了相应的文件I/O函数与通信函数,通过了解这些函数的使用,可以编制出符合不同需要的通信程序。与通信设备相关的结构有COMMCONFIG ,COMMPROP,COMMTIMEOUTS,COMSTAT,DCB,MODEMDEVCAPS,MODEMSETTINGS共7个,与通信有关的Windows API函数共有26个,具体说明可参考MSDN帮助文件。下面是打开串口的部分关键代码:
//以创建文件的形式打开文件,并将返回的端口句柄保存于句柄idComDev之中。 idComDev =CreateFile( g_szCom_Port[g_com_config.nPort], GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); …… file://cfg为COMMCONFIG结构的实例对象,获取当前通讯口的状态。 cfg.dcb.DCBlength = sizeof( DCB ) ; GetCommState( idComDev, &(cfg.dcb) ) ; file://设置发送、接收缓存大小 SetupComm( idComDev, 4096, 4096 ) ; // PurgeComm()是一个清除函数,它可以用来中止任何未决的后台读或写,并且可以冲掉I/O file://缓冲区.其中:PURGE_TXABORT 用于中止后台写操作;PRUGE_RXABORT用于中止后台 file://读操作 ;PRUGE_TXCLEAR用于清除发送缓冲区;PRUGE_RXCLEAR用于清除接收缓冲区 PurgeComm(idComDev,PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR); file://根据设置的参数填充DCB结构对象dcb的各个数据成员变量 dcb.DCBlength = sizeof( DCB ) ; GetCommState( idComDev, &dcb ) ; file://设置端口通讯参数 dcb.BaudRate =g_Com_Baud[g_com_config.nBaud]; dcb.ByteSize =g_Com_ByteSize[g_com_config.nData]; dcb.Parity =g_Com_Parity[g_com_config.nParity] ; dcb.StopBits =g_Com_StopBits[g_com_config.nStop]; file://硬件流控制 dcb.fDtrControl = DTR_CONTROL_DISABLE ; dcb.fOutxCtsFlow = FALSE ; dcb.fRtsControl = RTS_CONTROL_DISABLE ; file://软件流控制 dcb.fInX = dcb.fOutX = FALSE ; dcb.XonChar = (char)0xFF ; dcb.XoffChar = (char)0XFF ; dcb.XonLim = 100 ; dcb.XoffLim = 100 ; dcb.EvtChar=0x0d; dcb.fBinary = TRUE ; dcb.fParity = TRUE ;
file://超时控制的设置。超时有两种:区间超时:(仅对从端口中读取数据有用)它指定在读取两个字符之间要经历的时间;总超时: 当读或写特定的字节数需要的总时间超过某一阈值时,超时触发。计算超时可以根据公式: file://ReadTotalTimeout = (ReadTotalTimeoutMultiplier * bytes_to_read)+ // ReadToTaltimeoutConstant file://WriteTotalTimeout = (WriteTotalTimeoutMuliplier * bytes_to_write)+ // WritetoTotalTimeoutConstant file://如果在设置超时时参数为0则为无限等待,即无超时。 CommTimeOuts.ReadIntervalTimeout =MAXDWORD; CommTimeOuts.ReadTotalTimeoutMultiplier =0; CommTimeOuts.ReadTotalTimeoutConstant = 0 ; CommTimeOuts.WriteTotalTimeoutMultiplier =2*9600/dcb.BaudRate ; CommTimeOuts.WriteTotalTimeoutConstant = 25 ; SetCommTimeouts(idComDev , &CommTimeOuts ) ; file://根据设置好的dcb结构设置好通讯口的状态,并开启用于侦听端口,监视从外设传来的数 file://据的线程COMReadThreadProc。 if (SetCommState( idComDev, &dcb )) { m_bComPortOpen=TRUE; g_hCom=idComDev; AfxBeginThread(COMReadThreadProc,NULL,THREAD_PRIORITY_NORMAL); return; }
|
(二) 侦听监视线程
当成功的打开端口之后通过执行线程开启函数AfxBeginThread(COMReadThreadProc,NULL,THREAD_PRIORITY_NORMAL);开启了一个用于侦听端口的工作线程COMReadThreadProc。其具体处理过程
如下:
UINT COMReadThreadProc(LPVOID pParam) { …… file://设置读端口线程执行标志的标识 g_comthread.SetReadThreadKillFlag(FALSE); while(1) { file://读取端口开启状态的标识 if(TRUE==g_comthread.GetCloseCOMFlag()) { g_comthread.SetReadThreadKillFlag(TRUE); return 0;//正常关闭 } file://读端口操作 dwNeedRead=500; file://从端口读取数据到缓存中 if(!ReadFile(g_hCom,buf,dwNeedRead,&dwActRead,NULL)) { ClearCommError(g_hCom,&dwErrorMask,&comstat); PurgeComm(g_hCom,PURGE_RXCLEAR); continue; } file://读字符加入到全局缓冲 g_comreadbuf.Add(buf,dwActRead); Sleep(1); } …… return 0; }
|
其中用到的g_comthread和g_comreadbuf分别是线程类CCOMThread和读端口类COMReadBuf的实例对象。这两个类里都用类CCriticalSection m_Lock;实现了临界区技术,用以保持线程间的同步。CCOMReadBuf类的两个函数GetOneByte(……)、Add(……)分别用于从端口读取一个字符和向缓冲区添加读取的字符。其主要实现代码如下:
BOOL CCOMReadBuf::GetOneByte(BYTE *cb) { m_Lock.Lock(); if(m_nHead==m_nTail) { m_Lock.Unlock(); return FALSE;//空 } *cb=m_readbuf[m_nTail]; if(m_nTail < m_nBufSize-1) m_nTail++; else m_nTail=0; m_Lock.Unlock(); return TRUE;//空 }
void CCOMReadBuf::Add(BYTE buf[],int nBytes) { int nt,i; m_Lock.Lock(); for(i=0;i BR> { nt=(m_nHead-m_nTail); if(nt<0) nt+=m_nBufSize; if(nt+1==m_nBufSize) break;//缓冲区满 m_readbuf[m_nHead]=buf[i]; if(m_nHead < m_nBufSize-1) m_nHead++; else m_nHead=0; } m_Lock.Unlock(); }
|
(三) 控制命令的发送
控制命令可以从对话条上的编辑框获取,然后就可以通过写文件形式从端口发送出去,这部分实现起来较简单,也牵扯不到线程等技术。主要的代码主要有:
…… file://从对话条获取命令行 nRead=m_wndDlgBar.GetDlgItemText(IDC_EDIT_SEND,buf,500); file://向端口发送命令 if(nRead>0) { buf[nRead]=0x0d; buf[nRead+1]=0x00; ::WriteFile(g_hCom,buf,nRead+1,&dwActWrite,NULL); } ……
|