最近做實驗需要用到上位機顯示,所以編寫了這個USB-CAN上位機通訊程序。本程序是採用MFC編寫,通訊程序比較簡單,主要是調用API函數(API的操作請參考相關說明文檔)對下位機進行操作,下位機採集的數據再返回回上位機顯示,顯示部分使用了TeeChart控件,後面我會再次寫博客介紹該控件的使用,這篇文章主要講解如何編寫CAN通訊的上位機程序。
(1)建立工程
先建立基於對話框的MFC工程,將ControlCAN.lib,ControlCAN.DLL,ControlCAN.h這三個文件拷貝到工程目錄下,並在工程中添加頭文件
#include "ControlCAN.h"
接下來在項目屬性頁裏的配置屬性→連接器→輸入→附加依賴項中添加ControlCAN.lib這個文件,或者直接在工程中添加如下代碼:
#pragma comment(lib,"controlcan.lib")
再添加DevType(設備類型),DevIndex(設備索引)這兩個變量作爲類的成員變量,變量的初始化值如下所示:
CPV_Test01Dlg::CPV_Test01Dlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CPV_Test01Dlg::IDD, pParent)
// , m_chart(0)
, nDeviceType(4) //設備類型,對於USB-CAN2 配置爲4
, nDeviceInd(0) //設備索引,只有一個USB-CAN適配器時,索引號爲0
, nConnectFlag(0)
, nReceiveFlag(FALSE)
// , nFilterFlag(FALSE)
, szRXID(_T(""))
, szTXID(_T(""))
, szTXData(_T(""))
, pThread(NULL)
{
m_hIcon = AfxGetApp()->LoadIcon(IDI_ICON);
}
至此,和CAN有關的配置基本完成。
對話框如下所示:
(2)連接CAN
連接CAN的消息響應函數如下所示:
void CPV_Test01Dlg::OnBnClickedStart()
{
int nCANInd = 0; /* 第1個通道 */
DWORD dwRel;
VCI_INIT_CONFIG vic;
if(nConnectFlag == 0)
{
dwRel = VCI_OpenDevice(nDeviceType, nDeviceInd, 0);
if(!dwRel)
{
AfxMessageBox(_T("打開設備失敗!"));
return;
}
nConnectFlag = 1;
GetDlgItem(IDC_START) ->SetWindowTextW(_T("斷開CAN"));
vic.AccCode=0x80000008;
vic.AccMask=0xFFFFFFFF;
vic.Filter=1; //接收所有類型(擴展幀+標準幀)
vic.Timing0=0x00;
vic.Timing1=0x1C; //波特率500kbps
vic.Mode=0; //正常模式
dwRel = VCI_InitCAN(nDeviceType, nDeviceInd, nCANInd, &vic);
if(!dwRel)
{
AfxMessageBox(_T("初始化設備失敗!"));
return;
}
dwRel = VCI_StartCAN(nDeviceType, nDeviceInd, nCANInd);
if(!dwRel)
{
VCI_CloseDevice(nDeviceType, nDeviceInd);
AfxMessageBox(_T("啓動設備失敗!"));
return;
}
AfxMessageBox(_T("設備啓動成功!"));
}
else
{
dwRel = VCI_CloseDevice(nDeviceType, nDeviceInd);
if(dwRel != 1)
{
AfxMessageBox(_T("關閉設備失敗!"));
return;
}
nConnectFlag = 0;
GetDlgItem(IDC_START) ->SetWindowTextW(_T("連接CAN"));
AfxMessageBox(_T("設備關閉成功!"));
}
}
當點擊“連接CAN”按鈕時進入消息響應函數,根據標誌位nConnectFlag判斷設備是否已經連接。設備未連接時,調用API函數dwRel = VCI_OpenDevice(nDeviceType, nDeviceInd, 0)打開CAN設備,再將標誌位置一,同時將按鈕文字設置爲“斷開CAN”。
打開設備後再對設備進行初始化操作,先填充CAN初始化結構體VCI_INIT_CONFIG,主要是對波特率、收發模式、接收數據類型進行配置。配置好結構體之後調用CAN初始化函數dwRel = VCI_InitCAN(nDeviceType, nDeviceInd, nCANInd, &vic)初始化設備。
當設備連接成功後,標誌位nConnectFlag=1,再次點擊“斷開CAN”按鈕時調用關閉設備API函數dwRel = VCI_CloseDevice(nDeviceType, nDeviceInd)關閉CAN設備,再將標誌位置零,同時將按鈕文字設置爲“連接CAN”。
(3)發送數據
點擊發送按鈕,調用的消息響應函數如下所示:
void CPV_Test01Dlg::OnBnClickedSend()
{
UpdateData(TRUE);
DWORD dwRel;
CString str;
VCI_CAN_OBJ frameinfo;
int nCANInd = 0;
frameinfo.DataLen = 1;
frameinfo.RemoteFlag =0;
frameinfo.ExternFlag =1;
frameinfo.SendType =0;
frameinfo.ID =_wtoi(szTXID);
// frameinfo.ID =0xFF;
frameinfo.Data[0] = _wtoi(szTXData);
// frameinfo.Data[0] =123;
dwRel = VCI_Transmit(nDeviceType,nDeviceInd,nCANInd,&frameinfo,1); //發送幀數不宜過大
if(dwRel==-1)
{
AfxMessageBox(_T("數據發送失敗!"));
return;
}
// AfxMessageBox(_T("數據發送成功!"));
}
在發送數據函數中,先調用UpdateData(TRUE)將對話框中配置的數據(主要配置發送ID、數據類型、發送的數據等)調回到內存變量中,再配置CAN幀結構體VCI_CAN_OBJ,最後調用API發送數據函數dwRel = VCI_Transmit(nDeviceType,nDeviceInd,nCANInd,&frameinfo,1),該函數最後的一個變量爲發送的幀數量,通過測試發現,該值不能設置得過大,設置得過大會導致發送數據時接收數據出錯,一般以實際要發送的幀數量爲準。
(4)接收數據
下位機發送數據給上位機,通過配置接收線程來接收數據。點擊接收數據複選框時調用以下函數開啓線程,函數如下所示:
void CPV_Test01Dlg::OnBnClickedReceive()
{
UpdateData(TRUE);
if(nReceiveFlag)
{
pThread = AfxBeginThread(ReceiveThread,0); //開啓線程
nStopFlag=0;
}
else
{
nStopFlag=1;
}
}
在接收線程函數中接收數據,接收線程函數必須定義爲類的靜態成員函數或者全局函數,不然會報錯。接收線程函數如下所示:
unsigned int CPV_Test01Dlg::ReceiveThread(void* param)
{
CPV_Test01Dlg *dlg=(CPV_Test01Dlg*) AfxGetApp()->GetMainWnd(); //獲取主窗口指針
VCI_CAN_OBJ frameinfo[2500];
int len;
CString str,szID;
while(1)
{
len = VCI_Receive(dlg ->nDeviceType,dlg ->nDeviceInd,0,frameinfo,2500,0); //2500爲接收緩存區,儘量設大,避免數據溢出
if(len>=1)
for(int num=0;num<len;num++)
{
dlg ->GetDlgItemTextW(IDC_RXID,szID);
str.Format(_T("%d"),frameinfo[num].ID);
if(BST_UNCHECKED == dlg ->m_checkFilter.GetCheck()||
(BST_CHECKED == dlg ->m_checkFilter.GetCheck()&&str==szID))
{
dlg ->m_chart.Series(0).AddXY(nCount,frameinfo[num].Data[0],NULL,NULL);
str.Format(_T("%d"),frameinfo[num].Data[0]);
dlg ->SetDlgItemTextW(IDC_RXDATA,str);
nCount++;
str.Format(_T("%d"),nCount);
dlg ->SetDlgItemTextW(IDC_RXDATANUMBER,str);
}
}
Sleep(1); //進程延時1ms
if(nStopFlag)
break; //退出while循環
}
return TRUE;
}
接收線程函數爲類內的靜態成員函數,不能訪問類內的成員變量,通過*dlg=(CPV_Test01Dlg*) AfxGetApp()->GetMainWnd()獲取類的指針,進而訪問類內的成員變量。通過調用API函數len = VCI_Receive(dlg ->nDeviceType,dlg ->nDeviceInd,0,frameinfo,2500,0)來獲取接收數據的信息,接收幀信息存放在結構體變量frameinfo中。接收數據的緩存區儘可能的設大以防止數據的丟失。
(5)總結
下位機通過STM32單片機配置好後,下位機採集到的數據通過CAN收發器發送出來,最後在上位機中顯示。發送數據的速率不宜過快,太快會導致數據丟失。
上位機軟件界面如下所示:
提問方式:有啥不懂的可以隨時向我提問哈,掃描下方二維碼我會在第一時間給大家回覆的哈,謝謝。