多線程串口編程工具CserialPort類(附VC基於MFC單文檔協議通信源程序及詳細編程步驟)

 老有人以爲MSComm通信控件很土,更有人大聲疾呼:忘了它吧。確實當咱們對串口編程有了必定的瞭解後,應該用API函數寫一個屬於本身的串口程序,因爲編程者對程序瞭解,對程序修改自如。但我一直沒有中止過用MSComm通信控件,那麼簡單的東西,對付簡單的任務徹底能夠,但當咱們須要在程序中用多個串口,並且還要作不少複雜的處理,那麼最好不用MSComm通信控件,若是這時你還不肯意本身編寫底層,就用這個類:CserialPort類。

這是Remon Spekreijse寫的一個串口類, 地址在: 

http://codeguru.earthweb.com/network/serialport.shtml

類做者Remon Spekreijse已做了一個基於對話框的同時檢測4個串口示例的程序,在上面的網址和我主頁的串口源碼下載頁也能夠找到。我在這兒主要介紹如何將這個類應用到VC中基於文檔的程序中。爲了加深對串口數據處理的瞭解,咱們利用這個類解決以下問題:
html


問題:
web


串口2(COM2)每隔1秒向串口1(COM1)發送的NEMA格式的報文:串頭爲$,串尾爲*,中間爲一個xxxx的整數( 好比2345,不足4位則前面以0代替代),最後是hh校驗,規定hh爲xxxx四個數的半BYTE校驗和,最後加上回車<CR>與換行<LF>。整個數據包爲$xxxx*hh<CR><LF>。
串口1收到上述報文後,校驗正確後,將發來的數據顯示在視窗中,並記下發來的正確幀數和錯誤幀數,若正確,還向串口2發送Y,串口2收到Y後將收到的Y的計數顯示在視窗中。
測試方法:
將三線制串口線聯接上同一臺計算機的兩個串口,編好程序後就可測試。若是沒有兩個串口的微機,本身改改程序。

好了,你能夠先下載源程序: scporttest.zip(大小:49KB,VC6,WIN9X/2000,SerialPort.h SerialPort.cpp是兩個類文件)

編程步驟:
編程


◆1. 創建程序:
創建一個基於單文檔的MFC應用程序SCPortTest,全部步驟保持缺省狀態。
數組


◆2. 添加類文件:
將SerialPort.h SerialPort.cpp兩個類文件複製到工程文件夾中,用Project-Add to Project-Files命令將上述兩個文件加入工程。並在SCPortTestView.h中將頭文件SerialPort.h說明:#include "SerialPort.h"。
函數


◆3. 人工增長串口消息響應函數:OnCommunication(WPARAM ch, LPARAM port)
首先在SCPortTestView.h中添加串口字符接收消息WM_COMM_RXCHAR(串口接收緩衝區內有一個字符)的響應函數聲明:
//{{AFX_MSG(CSCPortTestView)
afx_msg LONG OnCommunication(WPARAM ch, LPARAM port);
//}}AFX_MSG
而後在SCPortTestView.cpp文件中進行WM_COMM_RXCHAR消息映射:
BEGIN_MESSAGE_MAP(CSCPortTestView, CView)
//{{AFX_MSG_MAP(CSCPortTestView)
ON_MESSAGE(WM_COMM_RXCHAR, OnCommunication)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
接着在SCPortTestView.cpp中加入函數的實現:
LONG CSCPortTestView::OnCommunication(WPARAM ch, LPARAM port)
{ ….. }
注意:因爲這個串口類加入工程後,沒有自動的消息映射機制,所以上述步驟均須要手工添加。
測試


◆4 初始化串口
在視建立時初始化串口,首先利用ClassWizardr按下圖生成OnInitialUpdate()函數。this


接着在SerialPort.h文件中說明咱們在程序中要用到的全局變量:
保存兩個串口接收數據:
char m_chChecksum; //用於COM1的校驗和計算
CString m_strRXhhCOM1; //用於存放COM1接收的半BYTE校驗字節hh
CString m_strRXDataCOM1; //COM1接收數據
CString m_strRXDataCOM2; //COM2接收數據
UINT m_nRXErrorCOM1; //COM1接收數據錯誤幀數
UINT m_nRXErrorCOM2; //COM2接收數據錯誤幀數
UINT m_nRXCounterCOM1; //COM1接收數據錯誤幀數
UINT m_nRXCounterCOM2; //COM2接收數據錯誤幀數CString

再在SerialPort.h文件中說明串口類對象:CSerailPort m_ComPort[2]; (public)。
由於要初始化2個串口,因此這裏用了數組。
下面是初始化串口1和串口2:
void CSCPortTestView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call the base class
m_chChecksum=0; //校驗和置0
m_nRXErrorCOM1=0; //COM1接收數據錯誤幀數置0
m_nRXErrorCOM2=0; //COM2接收數據錯誤幀數置0
m_nRXCounterCOM1=0; //COM1接收數據錯誤幀數置0
m_nRXCounterCOM2=0; //COM2接收數據錯誤幀數置0
m_strRXhhCOM1.Empty(); //清空半BYTE校驗hh存儲變量
for(int i=0;i<2;i++)
{
if (m_ComPort[i].InitPort(this,i+1,9600,'N',8,1,EV_RXFLAG | EV_RXCHAR,512))
//portnr=1(2),baud=960,parity='N',databits=8,stopsbits=1,
//dwCommEvents=EV_RXCHAR|EV_RXFLAG,nBufferSize=512
{
m_ComPort[i].StartMonitoring(); //啓動串口監視線程
if(i==1) SetTimer(1,1000,NULL); //設置定時器,1秒後發送數據
}
else
{
CString str;
str.Format("COM%d 沒有發現,或被其它設備佔用",i+1);
AfxMessageBox(str);
}
}
}

◆5 利用ClassWizard按下圖生成CSCPortTestView 的時間消息WM_TIMER響應函數:spa


void CSCPortTestView::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
int randdata=rand()%9000; //產生9000之內的隨機數
CString strSendData;
strSendData.Format("%04d",randdata);
SendString(strSendData, 2); //串口2發送數據;
CView::OnTimer(nIDEvent);
}

上面用到的SendString()需按以下方式生成:
在ClassView中單擊鼠標右鍵,在環境菜單中選擇Add Member Function:線程



void CSCPortTestView::SendString(CString &str, int Port)
{
char checksum=0,cr=CR,lf=LF;
char c1,c2;
for(int i=0;i<str.GetLength();i++)
checksum = checksum^str[i];
c2=checksum & 0x0f; c1=((checksum >> 4) & 0x0f);
if (c1 < 10) c1+= '0'; else c1 += 'A' - 10;
if (c2 < 10) c2+= '0'; else c2 += 'A' - 10;
CString str1;
str1='$'+str+"*"+c1+c2+cr+lf;
m_ComPort[Port-1].WriteToPort((LPCTSTR)str1);
}
請注意上面函數中是如何生成校驗碼的,要切記的是發送的校驗碼生成方式和對方接收的校驗檢測方式要一致。
code



◆6 在OnCommunication(WPARAM ch, LPARAM port)函數中進行數據處理
說明:WPARAM、 LPARAM 類型是多態數據類型(polymorphic data type),在WIN32中爲32位,支持多種數據類型,根據須要自動適應,這樣程序有很強的適應性。在此咱們能夠分別理解爲char和 integer 類型數據。
每當串口接收緩衝區內有一個字符時,就會產生一個WM_COMM_RXCHAR消息,觸發OnCommunication函數,這時咱們就能夠在函數中進行數據處理,因此這個消息就是整個程序的"發動機"。
下面是根據本文最初提出的問題寫出的處理函數:
LONG CSCPortTestView::OnCommunication(WPARAM ch, LPARAM port)
{
static int count1=0,count2=0,count3=0;
static char c1,c2;
static int flag;
CString strCheck="";

if(port==2) //COM2接收到數據
{
CString strtemp=(char)ch;
if(strtemp=="Y")
{
m_nRXCounterCOM2++;
CString strtemp;
strtemp.Format("COM2: NO.%06d", m_nRXCounterCOM2);
CDC* pDC=GetDC(); //準備數據顯示
pDC->TextOut(10,50,strtemp);//顯示接收到的數據
ReleaseDC(pDC);
}
}

if(port==1) //COM1接收到數據
{
m_strRXDataCOM1 += (char)ch;
switch(ch)
{
case '$':
m_chChecksum=0; //開始計算CheckSum
flag=0;
break;
case '*':
flag=2;
c2=m_chChecksum & 0x0f; c1=((m_chChecksum >> 4) & 0x0f);
if (c1 < 10) c1+= '0'; else c1 += 'A' - 10;
if (c2 < 10) c2+= '0'; else c2 += 'A' - 10;
break;
case CR:
break;
case LF:
m_strRXDataCOM1.Empty();
break;
default:
if(flag>0)
{
m_strRXhhCOM1 += ch; //獲得收到的校驗值hh
if(flag==1)
{
strCheck = strCheck+c1+c2; //計算獲得的校驗值hh
if(strCheck!=m_strRXhhCOM1) //若是校驗有錯
{
m_strRXDataCOM1.Empty();
m_nRXErrorCOM1++; //串口1錯誤幀數加1
}
else
{
m_nRXCounterCOM1++;
if(m_strRXDataCOM1.Left(1)=="$") //接收數據的第一個字符是$嗎?
{
char tbuf[6];
char *temp=(char*)((LPCTSTR)m_strRXDataCOM1);
tbuf[0]=temp[1]; tbuf[1]=temp[2];
tbuf[2]=temp[3]; tbuf[3]=temp[4];
tbuf[4]=0; //0表示字符串的結束,必要
int data=atoi(tbuf);
CString strDisplay1,strDisplay2;
strDisplay1.Format("NO. %06d: The reseived data is %04d",m_nRXCounterCOM1,data);
strDisplay2.Format("Error Counter=%04d.",m_nRXErrorCOM1);
CDC* pDC=GetDC(); //準備數據顯示
//int nColor=pDC->SetTextColor(RGB(255,255,0));
pDC->TextOut(10,10,strDisplay1);//顯示接收到的數據
pDC->TextOut(30,30,strDisplay2);//顯示錯誤幀數
//pDC->SetTextColor(nColor);
ReleaseDC(pDC);
}
CString str1="Y";
m_ComPort[0].WriteToPort((LPCTSTR)str1);//發送應答信號Y
}
m_strRXhhCOM1.Empty();
}
flag--;
}
else
m_chChecksum ^= ch;
break;
}

}
return 0;
}

 

感謝原做者。原文沒有做者簽名。

相關文章
相關標籤/搜索