程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 多線程,多接收模式串口類LsComm

多線程,多接收模式串口類LsComm

編輯:關於VC++

描述:一個串口通訊類

應用平台:Windows

版本: v1.0

主要功能:設計了一個簡潔易用的多線程串行通訊接口,可以切換查詢和自動接收模式,進行對串口數據收發的類

接觸VC,很不習慣mscomm等Active控件老讓人去注冊的方式,所以參照Delphi中的SpComm設計了一個類CComPort,對PJ Naughter 的CSerialPort進行了2次封裝,主要目的是簡化串口的使用.使其用簡單的代碼就可以完成串口通訊的過程.做了一個Demo程序演示了CComPort的使用,附圖如下:

下面我從如何使用和類的設計兩個方面說明一下: 

一 如何使用:

考慮到使用過程盡可能簡潔,實用,為了滿足不同的使用要求設計4種接收模式, 前兩種為手動接收方式,後兩種為自動類回調方式,下面是使用代碼

1.ManualReceiveByQuery 手動查詢接收

#include "ComPort.h"
LsComm::CComPort m_ComPort; //LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveByquery);
//ReCeive Com Data: 接收語句
DWORD InBufferCount;
byte pbuffer[2048];
InBufferCount = m_ComPort.GetInBufferCount();
if(InBufferCount>0)
{
   m_ComPort.GetInput(pbuffer,InBufferCount);
}
//Write Com Data: 寫串口數據
char a[10]="abcdefg";
this->m_ComPort.Output(a,sizeof(a));

2.ManualReceiveByConst(異步模式) 手動定常數接收模式

#include "ComPort.h"
LsComm::CComPort m_ComPort;//LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveByConst);
//ReCeive Com Data: //接收數據
DWORD InBufferCount=0;
byte pbuffer[2048];
InBufferCount=this->m_ComPort.GetInput(pbuffer,10,1000);
//上面我要在1000毫秒內接收10字節的數據,IbufferCount返回實際得到的數據
if(InBufferCount==0)
  return;
CString c;
char a[4];

for(int i=0;i<(int)InBufferCount;i++)
{
  ::sprintf(a,"%2.2X",pbuffer[i]);
  c+=a;
  c+=" ";
}
c="接收(Receive):"+c;

寫串口數據的過程同上

注意:第3,4種模式為自動接收模式,在用以前你必須先定義回調函數,然後,設置類的接收函數

/* 回調函數定義 */
void OnReceiveData(LPVOID pSender,void* pBuf,DWORD InBufferCount)
{
  CString c;
  byte a[100];
  char b[4]="";
  memcpy(a,pBuf,InBufferCount);
  CLsCommDemoDlg* pDlg = (CLsCommDemoDlg*) pSender;
  CListBox*pList =(CListBox*)pDlg->GetDlgItem(IDC_LIST1);
  for(int i=0;i<(int)InBufferCount;i++)
  {
   ::sprintf(b,"%2.2X",a[i]);
    c+=" ";
    c+=b;
  }
  c="接收(Receive):"+c;
  pList->AddString(c);
}

3.AutoReceiveBySignal 自動信號接收模式

#include "ComPort.h"
LsComm::CComPort m_ComPort;//LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveBySignal );
m_ComPort.SetReceiveFunc((FOnReceiveData)OnReceiveData,this);
m_ComPort.SetBreakHandleFunc(OnComBreak);
//ReCeive Com Data:接收數據函數
in OnReceiveData(LPVOID pSender,void* pBuf,DWORD InBufferCount) //above
//write data
the same as the first mode;

4.AutoReceiveByBreak 中斷接收模式

#include "ComPort.h"
LsComm::CComPort m_ComPort;//LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveByBreak );
m_ComPort.SetReceiveFunc((FOnReceiveData)OnReceiveData,this);
//ReCeive Com Data:接收數據函數
in OnReceiveData(LPVOID pSender,void* pBuf,DWORD InBufferCount) //above
//write data
the same as the first mode;

另外說明2點:

(1)如果你需要處理中斷事件,你可以在每種模式中設置中斷接收事件:如下

//定義中斷事件接收函數 
void OnComBreak(LPVOID pSender,DWORD dwMask,COMSTAT stat)
{
   //deal with the break of com here
}
m_ComPort.SetBreakHandleFunc(OnComBreak); //設置中斷事件

(2)如何處理如,改變波特率,以及其它參數呢?

m_ComPort.GetSerialPort()可以獲得一個CSerialPort類的指針,如何就可以操作各種com屬性了.

DCB dcb;
this->m_ComPort.GetSerialPort()->GetState(dcb);

二.類的設計與編程

1. 類結構

為了說明一個大概的類構成,我用Rose畫了一下類圖:如下

CComPort內部聚合了一個CSerialPort的串口類,並與一個CReadComThread線程關聯,讓其去讀取串口數據.

LsComm::CComPort m_ComPort;//LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveBySignal );
m_ComPort.SetReceiveFunc(OnReceiveData,this);
m_ComPort.SetBreakHandleFunc(OnComBreak);

這些語句是怎麼實現串口數據的發送和讀取的呢?

2. 打開過程CComPort::Open()

void CComPort::Open(int nPort,ReceiveMode mode, DWORD dwBaud, Parity parity, BYTE DataBits,
      StopBits stopbits,FlowControl fc)
{ 
  //1.新建串口
  this->m_pPort = new CSerialPort();
  //2.判斷收發模式
  if(mode==ReceiveMode::ManualReceiveByQuery)
  {
    this->m_IsOverlapped = false;
  }
  else
  {
    this->m_IsOverlapped = true;
  }
  this->m_RecvMode = mode;

  //3.轉換參數,打開串口
  int index;
  index=parity-CComPort::EvenParity;
  CSerialPort::Parity spParity=(CSerialPort::Parity)(CSerialPort::EvenParity+index);
  …略去  
  this->m_pPort->Open(nPort,dwBaud,spParity,DataBits,spStopbits,spFC,m_IsOverlapped);
  this->m_pPort->Setup(4096,4096);
  this->m_pPort->Purge(PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
  //it is important!!
  COMMTIMEOUTS timeouts;
  this->m_pPort->GetTimeouts(timeouts);
  timeouts.ReadIntervalTimeout=100;
  this->m_pPort->SetTimeouts(timeouts);
  this->m_CurPortNum = nPort;
   //創建關閉事件
  this->m_hCloseEvent = CreateEvent(NULL,true,false,NULL);
  ASSERT(this->m_hCloseEvent);
  //4.創建線程類
  this->m_pReadThread = new CReadComThread();
  this->m_pReadThread->BandSerialPort(this);
  this->m_pReadThread->Create();
  this->m_pReadThread->Resume();
  if(this->IsOverlapped())
  {
    this->m_hWriteEvent = ::CreateEvent(NULL,false,false,NULL);
  }
}

主要做的工作是:

新建串口 this->m_pPort = new CSerialPort();

打開串口 this->m_pPort->Open

創建讀取線程 this->m_pReadThread = new CReadComThread();

設立線程類與CComPort的關聯關系this->m_pReadThread->BandSerialPort(this);

void CReadComThread::BandSerialPort(CComPort* pPort)
{
  ASSERT(pPort);
  this->m_pPort = pPort;
  //創建異步讀取事件

  if(this->m_pPort->IsOverlapped())
  {
    this->m_ReadOverlapped.hEvent =::CreateEvent(NULL,false,false,NULL);
    ASSERT(this->m_ReadOverlapped.hEvent);
    this->m_BreakOverlapped.hEvent = ::CreateEvent(NULL,false,false,NULL);
    ASSERT(this->m_BreakOverlapped.hEvent);
  }

}

模式主要在線程執行的過程中發揮作用

3.串口的發送數據過程

DWORD CComPort::Output(void* pBuf,DWORD Count)
{ 
  DWORD dwWriteBytes=0;
  if(this->IsOverlapped())//異步模式
  {
    this->m_pPort->Write(pBuf,Count,this->m_WriteOverlapped);
    if(WaitForSingleObject(this->m_WriteOverlapped.hEvent,INFINITE)==WAIT_OBJECT_0)
    {
      this->m_pPort->GetOverlappedResult(this->m_WriteOverlapped,dwWriteBytes,false);
    }
  }
  else
    dwWriteBytes= this->m_pPort->Write(pBuf,Count);
  return dwWriteBytes;
}

再看this->m_pPort->Write(pBuf,Count);

實際上是:調用

DWORD CSerialPort::Write(const void* lpBuf, DWORD dwCount)
{
 ASSERT(IsOpen());
 ASSERT(!m_bOverlapped);
 DWORD dwBytesWritten = 0;
 if (!WriteFile(m_hComm, lpBuf, dwCount, &dwBytesWritten, NULL))
 {
  TRACE(_T("Failed in call to WriteFile\n"));
  AfxThrowSerialException();
 }
 return dwBytesWritten;
}

或者是BOOL CSerialPort::Write(const void* lpBuf, DWORD dwCount, OVERLAPPED& overlapped, DWORD* pBytesWritten) 異步寫串口的過程

4.串口的讀取過程

分兩種:查詢讀取和線程自動讀取

(1) 查詢讀取

DWORD CComPort::GetInput(void* pBuf,DWORD Count,DWORD dwMilliseconds)
{ 
  //不能在自動模式下getinput
  if(this->GetReceiveMode()==CComPort::AutoReceiveByBreak||
    this->GetReceiveMode()==CComPort::AutoReceiveBySignal)
  {
    ::AfxMessageBox("Can''t use GetInput methord in this mode!");
    return 0;
  }
  if(this->IsOverlapped())
  {
    ASSERT(this->m_pReadThread);
    DWORD dwBytes = this->m_pReadThread->ReadInput(pBuf,Count,dwMilliseconds);
    this->m_pPort->TerminateOutstandingReads();
    return dwBytes;
  }
  else
    return this->m_pPort->Read(pBuf,Count);
}

主要是調用m_pPort->Read(pBuf,Count);然後調用API函數ReadFile

(2) 線程等待處理

主要過程:在線程CreadComThread的Execute中

void CReadComThread::Execute()
{
  if(this->m_pPort->GetReceiveMode()==CComPort::ManualReceiveByQuery)
  {
    this->ExecuteByManualQueryRecvMode();
  }
  else if(this->m_pPort->GetReceiveMode()==CComPort::ManualReceiveByConst)
  {
    this->ExecuteByManualConstRecvMode();
  }
  else if(this->m_pPort->GetReceiveMode()==CComPort::AutoReceiveBySignal)
  {
    this->ExecuteByAutoSignalRecvMode();
  }
  else//中斷模式
  {
    this->ExecuteByAutoBreakRecvMode();
  }
}

主要是選擇模式然後執行: 下面看看this->ExecuteByAutoSignalRecvMode();

void CReadComThread::ExecuteByAutoSignalRecvMode()
{
  DWORD dwMask=0;
  HANDLE WaitHandles[3]; //監聽事件數組
  DWORD dwSignaledHandle;
  WaitHandles[0] = this->m_pPort->GetCloseHandle();
  WaitHandles[1] = this->m_ReadOverlapped.hEvent;
  WaitHandles[2] = this->m_BreakOverlapped.hEvent;
  this->m_pPort->GetSerialPort()->SetMask(EV_ERR | EV_RLSD | EV_RING );
  if(!SetBreakEvent(dwMask))
    goto EndThread;
  //設置讀事件
  if(!SetReadEvent(this->m_ReadOverlapped))
    goto EndThread;
  //設置comEvent
  for(;;)
  {
    dwSignaledHandle=::WaitForMultipleObjects(3,WaitHandles,false,INFINITE);
    switch(dwSignaledHandle)
    {
    case WAIT_OBJECT_0:
      goto EndThread;
      break;
    case WAIT_OBJECT_0+1:
    if(!this->HandleReadEvent(this->m_ReadOverlapped))
      goto EndThread;
    if(!this->SetReadEvent(this->m_ReadOverlapped))
      goto EndThread;
    break;
    case WAIT_OBJECT_0+2:
    if(!this->HandleBreakEvent(dwMask))
      goto EndThread;
    if(!this->SetBreakEvent(dwMask))
      goto EndThread;
    break;
    default:
       //goto EndThread;
    break;
    }

  }
  EndThread:
    this->m_pPort->GetSerialPort()->Purge(PURGE_RXABORT | PURGE_RXCLEAR);
    ::CloseHandle(this->m_ReadOverlapped.hEvent);
    ::CloseHandle(this->m_BreakOverlapped.hEvent);
    return ;
}

主要是一個等待事件發送然後調用,響應的過程,如果讀取事件發生則調用this->HandleReadEvent(this->m_ReadOverlapped);

bool CReadComThread::HandleReadEvent(OVERLAPPED& overlapped)
{
  if(this->m_pPort->GetSerialPort()->GetOverlappedResult(overlapped,this->m_InBufferCount,false))
  {
    return this->HandleData();
  }
  DWORD dwError = ::GetLastError();
  if(dwError==ERROR_INVALID_HANDLE)
    return false;
  else
    return true;
}

如果查詢有數據,則this->HandleData();

bool CReadComThread::HandleData() //處理讀取數據
{
  if(this->m_InBufferCount>0)
  {
   this->m_pBuffer = new byte[this->m_InBufferCount];
   for(int i=0;i<(int)this->m_InBufferCount;i++)
   {
     this->m_pBuffer[i] = this->m_InputBuffer[i];
   }
   this->m_pPort->ReceiveData(this->m_pBuffer,this->m_InBufferCount);
   delete[] this->m_pBuffer;
  }
  return true;
}

在這調用了this->m_pPort->ReceiveData(this->m_pBuffer,this->m_InBufferCount);即調用了你傳入的函數.整個讀取過程就這樣了.如果還有不明白,請看我寫的CComPort的類的代碼.

當然,由於串行通訊各種情況綜合在一起比較復雜,另外本人水平有限,所以一時很難考慮全面,這個版本暫時定為1.0,希望大家如果在使用過程中發現什麼問題,請及時的告訴偶(E-Mail:[email protected]),有時間我做個升級什麼的,當然,希望大家多多提出批評意見.

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved