DSP

用VC6.0实现上位机串口通信

2019-07-13 16:52发布

串口是常用的计算机与外部串行设备之间的数据传输通道,由于串行通信方便易行,所以应用广泛。我们可以利用Windows API 提供的通信函数编写出高可移植性的串行通信程序。本实例介绍在Visual C++6.0下如何利用Win32 API 实现串行通信程序。程序编译运行后的界面效果如图一所示: 
 
图一、串口通信示例程序
  一、实现方法

  在Win16中,可以利用OpenComm()、CloseComm()和WriteComm()等函数打开、关闭和读写串口。但在Win32中,串口和其他通信设备均被作为文件处理,串口的打开、关闭和读写等操作所用的API函数与操作文件的函数相同。可通过CreateFile()函数打开串口;通过CloseFile()函数关闭串口;通过DCB结构、CommProp()、GetCommProperties()、SetCommProperties()、GetCommState()及SetCommState()等函数设置串口状态,通过函数ReadFile()和WritFile()等函数读写串口。下面来详细介绍其实现原理。 

  对于串行通信设备,Win32 API支持同步和异步两种I/O操作。同步操作方式的程序设计相对比较简单,但I/O操作函数在I/O操作结束前不能返回,这将挂起调用线程,直到I/O操作结束。异步操作方式相对要复杂一些,但它可让耗时的I/O操作在后台进行,不会挂起调用线程,这在大数据量通信的情况下对改善调用线程的响应速度是相当有效的。异步操作方式特别适合同时对多个串行设备进行I/O操作和同时对一个串行设备进行读/写操作。

  串行设备的初始化 

  串行设备的初始化是利用CreateFile()函数实现的。该函数获得串行设备句柄并对其进行通信参数设置,包括设置输出/接收缓冲区大小、超时控制和事件监视等。 例如下面的代码实现了串口的初始化: 复制代码 //串行设备句柄; HANDLE hComDev=0; //串口打开标志; BOOL bOpen=FALSE; //线程同步事件句柄; HANDLE hEvent=0; DCB dcb; COMMTIMEOUTS timeouts; //设备已打开 if(bOpen) return FALSE;  //打开COM1 if((hComDev=CreateFile(“COM1”,GENERICREAD|GENERICWRITE,0,NULL,OPENEXISTING,FILEATTRIBUTENORMAL,NULL))==INVALIDHANDLEVALUE)   return FALSE; //设置超时控制 SetCommTimeouts(hComDev,&timeouts); //设置接收缓冲区和输出缓冲区的大小 SetupComm(hComDev,1024,512); //获取缺省的DCB结构的值 GetCommState(hComDev,&dcb); //设定波特率为9600 bps  dcb.BaudRate=CBR9600; //设定无奇偶校验  dcb.fParity=NOPARITY; //设定数据位为8  dcb.ByteSize=8;  //设定一个停止位  dcb.StopBits=ONESTOPBIT; //监视串口的错误和接收到字符两种事件  SetCommMask(hComDev,EVERR|EVRXCHAR); //设置串行设备控制参数  SetCommState(hComDev,&dcb); //设备已打开  bOpen=TRUE;  //创建人工重设、未发信号的事件  hEvent=CreateEvent(NULL,FALSE,FALSE, “WatchEvent”); //创建一个事件监视线程来监视串口事件  AfxBeginThread(CommWatchProc,pParam); } 复制代码     在设置串口DCB结构的参数时,不必设置每一个值。首先读出DCB缺省的参数设置,然后只修改必要的参数,其他参数都取缺省值。由于对串口进行的是同步I/O操作,所以除非指定进行监测的事件发生,否则WaitCommEvent()函数不会返回。在串行设备初始化的最后要建立一个单独的监视线程来监视串口事件,以免挂起当前调用线程,其中pParam可以是一个对事件进行处理的窗口类指针。 
 
  如果要进行异步I/O操作,打开设备句柄时,CreateFile的第6个参数应增加FILEFLAGOVERLAPPED 标志。 

  数据发送 

  数据发送利用WriteFile()函数实现。对于同步I/O操作,它的最后一个参数可为NULL;而对异步I/O操作,它的最后一个参数必需是一个指向OVERLAPPED结构的指针,通过OVERLAPPED结构来获得当前的操作状态。  复制代码 BOOL WriteComm(LPCVOID lpSndBuffer,DWORD dwBytesToWrite) {  //lpSndBuffer为发送数据缓冲区指针,  dwBytesToWrite为将要发送的字节长度  //设备已打开  BOOL bWriteState;  //实际发送的字节数  DWORD dwBytesWritten;  //设备未打开  if(!bOpen) return FALSE;  bWriteState=WriteFile(hComDev,lpSndBuffer,dwBytesToWrite,&dwBytesWritten,NULL);  if(!bWriteState || dwBytesToWrite!=dwBytesWritten)   //发送失败   return FALSE;  else   //发送成功   return TRUE; } 复制代码  
  数据接收 

  接收数据的任务由ReadFile函数完成。该函数从串口接收缓冲区中读取数据,读取数据前,先用ClearCommError函数获得接收缓冲区中的字节数。接收数据时,同步和异步读取的差别同发送数据是一样的。  复制代码 DWORD ReadComm(LPVOID lpInBuffer,DWORD dwBytesToRead) {  //lpInBuffer为接收数据的缓冲区指针, dwBytesToRead为准备读取的数据长度(字节数)  //串行设备状态结构  COMSTAT ComStat;  DWORD dwBytesRead,dwErrorFlags;   //设备未打开  if(!bOpen) return 0;  //读取串行设备的当前状态  ClearCommError(hComDev,&dwErrorFlags,&ComStat);  //应该读取的数据长度  dwBytesRead=min(dwBytesToRead,ComStat.cbInQue);  if(dwBytesRead>0)   //读取数据   if(!ReadFile(hComDev,lpInBuffer,dwBytesRead,&dwBytesRead,NULL))    dwBytesRead=0;  return dwBytesRead; } 复制代码  
  事件监视线程 

  事件监视线程对串口事件进行监视,当监视的事件发生时,监视线程可将这个事件发送(SendMessage)或登记(PostMessage)到对事件进行处理的窗口类(由pParam指定)中。  复制代码 UINT CommWatchProc(LPVOID pParam) {  DWORD dwEventMask=0; //发生的事件;  while(bOpen)  {   //等待监视的事件发生   WaitCommEvent(hComDev, &dwEventMask,NULL);   if ((dwEventMask & EVRXCHAR)==EVRXCHAR)    ……//接收到字符事件后,可以将此消息登记到由pParam有指定的窗口类中进行处理   if(dwEventMask & EVERR)==EVERROR)    ……//发生错误时的处理  }  SetEvent(hEvent);  //发信号,指示监视线程结束  return 0; } 复制代码     关闭串行设备 

  在整个应用程序结束或不再使用串行设备时,应将串行设备关闭,包括取消事件监视,将设备打开标志bOpen置为FALSE以使事件监视线程结束,清除发送/接收缓冲区和关闭设备句柄。  复制代码 void CloseSynComm() {  if(!bOpen) return;  //结束事件监视线程  bOpen=FALSE;  SetCommMask(hComDev,0);  //取消事件监视,此时监视线程中的WaitCommEvent将返回  WaitForSingleObject(hEvent,INFINITE);  //等待监视线程结束  CloseHandle(hEvent); //关闭事件句柄  //停止发送和接收数据,并清除发送和接收缓冲区  PurgeComm(hComDev,PURGETXABORT| PURGERXABORT|PURGETXCLEAR|PURGERXCLEAR);  //关闭设备句柄  CloseHandle(hComDev); } 复制代码  
  二、编程步骤

  1、 启动Visual C++6.0,生成一个基于对话框的的应用程序,将该程序命名为“SerealCom”;

  2、 按照图一的界面设计对话框,具体设置参见代码部分;

  3、 使用Class Wizard为对话框的按钮添加鼠标单击消息响应函数;

  4、 添加代码,编译运行程序。 复制代码 1 #if !defined(_COMM_ACCESS_FUNCTIONS_AND_DATA) 2 #define _COMM_ACCESS_FUNCTIONS_AND_DATA 3 #if _MSC_VER > 1000 4 #pragma once 5 #endif // _MSC_VER > 1000 6 #define EVENTCHAR 0x0d 7 #define MAXBLOCKLENGTH 59 8 9 extern BYTE XwCom; 10 extern BYTE sCom1[5],sCom2[MAXBLOCKLENGTH+12]; 11 extern sCom3[MAXBLOCKLENGTH+12]; 12 extern BYTE opation; 13 extern short ComNum; 14 15 #define FC_DTRDSR 0x01 16 #define FC_RTSCTS 0x02 17 #define FC_XONXOFF 0x04 18 #define ASCII_BEL 0x07 19 #define ASCII_BS 0x08 20 #define ASCII_LF 0x0A 21 #define ASCII_CR 0x0D 22 #define ASCII_XON 0x11 23 #define ASCII_XOFF 0x13 24 25 class CComStatus 26 { 27  public: 28   HANDLE m_hCom; 29   BYTE m_bComId; 30   BYTE m_bByteSize; 31   BYTE m_bStopBits; 32   BYTE m_bParity; 33   DWORD m_dwBaudRate; 34 35   //WORD m_fChEvt; 36 37   char m_bEvtChar; 38   DWORD m_fBinary; 39   BOOL m_bConnected; 40   BOOL m_fXonXoff; 41   BOOL m_bFlowCtrl; 42   OVERLAPPED m_rdos; 43   OVERLAPPED m_wtos; 44 45   //functions 46 47   CComStatus(); 48   CComStatus(BYTE bComId,BYTE bByteSize,BYTE bStopBits,BYTE bParity, 49     DWORD dwBaudRate,/*WORD fChEvt,*/char bEvtChar,DWORD fBinary); 50   BOOL OpenConnection(); 51   BOOL CloseConnection(); 52   BOOL SetupConnection(); 53   BOOL IsConnected(); 54 }; 55 56 UINT CommWatchProc( LPVOID lpData ); 57 BOOL WriteCommBlock( CComStatus& comDev, LPSTR lpByte , DWORD dwBytesToWrite); 58 int ReadCommBlock(CComStatus& comDev,LPSTR lpszBlock, int nMaxLength ); 59 int ReadCommBlockEx(CComStatus& comDev,LPSTR lpszBlock, int nMaxLength,DWORD dwTimeOut); 60 #endif 61 62 /////////////////////////////////////////////////////////////////////// 63 64 #include "stdafx.h" 65 #include "com232.h" 66 67 BYTE XwCom=0x40; 68 BYTE sCom1[5],sCom2[MAXBLOCKLENGTH+12],sCom3[MAXBLOCKLENGTH+12]; 69 BYTE opation; 70 short ComNum; 71 CComStatus::CComStatus() 72 { 73  m_hCom = NULL; 74  m_bComId = (char)ComNum;//COM1 75  m_bByteSize=8; 76  m_bStopBits=ONESTOPBIT; 77  m_bParity=NOPARITY; 78  m_dwBaudRate=9600; 79  m_bEvtChar=EVENTCHAR; 80  m_fBinary=1; 81  m_bConnected = FALSE; 82  m_bFlowCtrl = FC_XONXOFF ; 83  m_fXonXoff = FALSE; 84 } 85 86 CComStatus::CComStatus(BYTE bComId,BYTE bByteSize,BYTE bStopBits,BYTE bParity,DWORD dwBaudRate,/*WORD fChEvt,*/char bEvtChar,DWORD fBinary) 87 { 88  m_hCom = NULL; 89  m_bComId = bComId; 90  m_bByteSize=bByteSize; 91  m_bStopBits=bStopBits; 92  m_bParity=bParity; 93  m_dwBaudRate=dwBaudRate; 94  m_bEvtChar=bEvtChar; 95  m_fBinary=fBinary; 96  m_bConnected = FALSE; 97  m_bFlowCtrl = FC_XONXOFF ; 98  m_fXonXoff = FALSE; 99 } 100 101 BOOL CComStatus::OpenConnection() 102 { 103  char csCom[10]; 104  COMMTIMEOUTS CommTimeOuts ; 105  if((m_bComId < 0) || (m_bComId > 4)) 106   return FALSE;//从COM1到COM4 107  if(m_hCom)//if already open 108  return FALSE; 109 110  //OVERLAPPED包含异步I/O信息 111 112  m_rdos.Offset = 0; 113  m_rdos.OffsetHigh = 0; 114  m_rdos.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); 115  if(m_rdos.hEvent == NULL) 116   return FALSE; 117  m_wtos.Offset = 0; 118  m_wtos.OffsetHigh = 0; 119  m_wtos.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); 120  if(m_wtos.hEvent == NULL) 121  { 122   CloseHandle(m_rdos.hEvent); 123   return FALSE; 124  } 125 126  wsprintf(csCom,"COM%d",m_bComId); 127 128  m_hCom = CreateFile(csCom,GENERIC_READ | GENERIC_WRITE, 0,NULL, OPEN_EXISTING,ILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,NULL); 129 130  if(m_hCom == INVALID_HANDLE_VALUE) { 131   //dwError = GetLastError(); 132   // handle error 133   return FALSE; 134  } 135  else 136  { 137   SetCommMask( m_hCom, EV_RXCHAR ) ; // get any early notifications 138   SetupComm( m_hCom, 4096, 4096 ) ; // setup device buffers 139   // purge any information in the buffer 140 141   PurgeComm( m_hCom, PURGE_TXABORT | PURGE_RXABORT |PURGE_TXCLEAR | PURGE_RXCLEAR ) ; 142 143   // set up for overlapped I/O 144 145   DWORD dwTemp = 1000 / (this->m_dwBaudRate / 8); 146   CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF ; 147   CommTimeOuts.ReadTotalTimeoutMultiplier = 0;//((dwTemp > 0) ? dwTemp : 1); 148   CommTimeOuts.ReadTotalTimeoutConstant = 1000 ; 149 150   // CBR_9600 is approximately 1byte/ms. For our purposes, allow 151   // double the expected time per character for a fudge factor. 152 153   CommTimeOuts.WriteTotalTimeoutMultiplier =2*CBR_9600/this->m_dwBaudRate;//( npTTYInfo ) ; 154   CommTimeOuts.WriteTotalTimeoutConstant = 0;//1000 ; 155 156   SetCommTimeouts( m_hCom, &CommTimeOuts ) ; 157  } 158  if(!SetupConnection()) 159  { 160   CloseConnection(); 161   return FALSE; 162  } 163  EscapeCommFunction( m_hCom, SETDTR ); 164  m_bConnected = TRUE; 165  return TRUE; 166 } 167 168 BOOL CComStatus::CloseConnection() 169 { 170  if (NULL == m_hCom) 171   return ( TRUE ) ; 172  // set connected flag to FALSE 173  m_bConnected = FALSE; 174  // disable event notification and wait for thread 175  // to halt 176  SetCommMask( m_hCom, 0 ) ; 177  EscapeCommFunction( m_hCom, CLRDTR ) ; 178  // purge any outstanding reads/writes and close device handle 179  PurgeComm( m_hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ; 180  CloseHandle( m_hCom ) ; 181  m_hCom = NULL; 182 183  // change the selectable items in the menu 184 185  CloseHandle(m_rdos.hEvent); 186  CloseHandle(m_wtos.hEvent); 187  return ( TRUE ) ; 188 } 189 190 BOOL CComStatus::SetupConnection() 191 { 192  BOOL fRetVal ; 193  BYTE bSet ; 194  DCB dcb ; 195  if(m_hCom == NULL) 196   return FALSE; 197  dcb.DCBlength = sizeof( DCB ) ; 198  GetCommState( m_hCom, &dcb ) ; 199  dcb.BaudRate = this->m_dwBaudRate; 200  dcb.ByteSize = this->m_bByteSize; 201  dcb.Parity = this->m_bParity; 202  dcb.StopBits = this->m_bStopBits ; 203  dcb.EvtChar = this->m_bEvtChar ; 204  // setup hardware flow control 205  bSet = (BYTE) ((m_bFlowCtrl & FC_DTRDSR) != 0) ; 206  dcb.fOutxDsrFlow = bSet ; 207  if (bSet) 208   dcb.fDtrControl = DTR_CONTROL_HANDSHAKE ; 209  else 210   dcb.fDtrControl = DTR_CONTROL_ENABLE ; 211  bSet = (BYTE) ((m_bFlowCtrl & FC_RTSCTS) != 0) ; 212  dcb.fOutxCtsFlow = bSet ; 213  if (bSet) 214   dcb.fRtsControl = RTS_CONTROL_HANDSHAKE ; 215  else 216   dcb.fRtsControl = RTS_CONTROL_ENABLE ; 217  // setup software flow control 218  bSet = (BYTE) ((m_bFlowCtrl & FC_XONXOFF) != 0) ; 219  dcb.fInX = dcb.fOutX = bSet ; 220  dcb.XonChar = ASCII_XON ; 221  char xon = ASCII_XON ; 222  dcb.XoffChar = ASCII_XOFF ; 223  char xoff = ASCII_XOFF ; 224  dcb.XonLim = 100 ; 225  dcb.XoffLim = 100 ; 226  // other various settings 227  dcb.fBinary = TRUE ; 228  dcb.fParity = TRUE ; 229  fRetVal = SetCommState( m_hCom, &dcb ) ; 230  return ( fRetVal ) ; 231 } // end of SetupConnection() 232 233 BOOL CComStatus::IsConnected() 234 { 235  return m_bConnected; 236 } 237 238 UINT CommWatchProc( LPVOID lpData ) 239 { 240  DWORD dwEvtMask ; 241  //NPTTYINFO npTTYInfo = (NPTTYINFO) lpData ; 242  OVERLAPPED os ; 243  int nLength ; 244  //BYTE abIn[ MAXBLOCK + 1] ; 245 246  CComStatus * pCom = (CComStatus *)lpData; 247  memset( &os, 0, sizeof( OVERLAPPED ) ) ; 248  // create I/O event used for overlapped read 249 250  os.hEvent = CreateEvent( NULL, // no security 251   TRUE, // explicit reset req 252   FALSE, // initial event reset 253   NULL ) ; // no name 254 255  if (os.hEvent == NULL) 256  { 257   MessageBox( NULL, "Failed to create event for thread!", "TTY Error!",MB_ICONEXCLAMATION | MB_OK ) ; 258   return ( FALSE ) ; 259  } 260  if (!SetCommMask( pCom->m_hCom, EV_RXCHAR )) 261   return ( FALSE ) ; 262  char buf[256]; 263  while ( pCom->m_bConnected ) 264  { 265   dwEvtMask = 0 ; 266   WaitCommEvent( pCom->m_hCom, &dwEvtMask, NULL ); 267   if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR) 268   { 269    if ((nLength = ReadCommBlock( *pCom, (LPSTR) buf, 255 ))) 270    { 271     //WriteTTYBlock( hTTYWnd, (LPSTR) abIn, nLength ) ; 272     buf[nLength]='