使用的USB轉CAN的設備是周立功的USBCAN-II,在購買的時候,會有上位機二次開發的庫文件、例程和API文檔等材料,可以參考。
1、庫函數的調用
首先,把庫函數文件都放在工作目錄下。庫函數文件總共有三個文件:ControlCAN.h、ControlCAN.lib、ControlCAN.dll和一個文件夾kerneldlls。
VC調用動態庫的方法
(1) 在擴展名為.CPP的文件中包含ControlCAN.h頭文件。
如:#include “ControlCAN.h”
(2) 在工程的連接器設置中連接到ControlCAN.lib文件。
如:在VC7環境下,在項目屬性頁里的配置屬性→連接器→輸入→附加依賴項中添加ControlCAN.lib
中間換了一台電腦,出現電腦丟失ControlCAN.dll的問題,將ControlCAN.dll拷到了可執行文件的文件夾中即可
2、基本操作
2.1 連接設備
我這里每次連接都會重新開啟接收數據的線程,創建一次接收數據的txt文檔
void CTest_OilDlg::OnBnClickedButtonConnect() { //首先判斷CAN是否打開,,如果已經打開,則先復位及重啟CAN--1.8 //關閉程序前必須點擊斷開連接按鈕,否則報錯 if(m_connect == 1) { m_connect = 0; //isShow = 0; Sleep(500); GetDlgItem(IDC_BUTTON_CONNECT)->SetWindowTextW(_T("連接")); VCI_CloseDevice(m_deviceType,m_deviceIndex); showListInfo(_T("斷開設備成功")); //結束自發自收測試的定時器 KillTimer(0); //結束當前線程 if(m_pThread != NULL) { //::WaitForSingleObject(m_pThread->m_hThread,INFINITE);//該函數會造成死鎖 //https://blog.csdn.net/silvervi/article/details/5874212 將上面函數修改成如下,以避免上面函數阻塞對話框主線程的消息隊列 DWORD dwRet = 0; MSG msg; while(true) { //等待處理數據線程結束,和等待消息隊列中的任何消息 dwRet = MsgWaitForMultipleObjects(1,&m_pThread->m_hThread,false,INFINITE,QS_ALLINPUT); //dwRet = WaitForSingleObject(m_pThread->m_hThread,50); switch (dwRet) { case WAIT_OBJECT_0: break; case WAIT_OBJECT_0 + 1: //get the message from Queue and dispatch it to specific window PeekMessage(&msg,NULL,0,0,PM_REMOVE); DispatchMessage(&msg); continue; default: break; } break; } //CloseHandle(m_pThread->m_hThread); delete m_pThread; m_pThread = NULL;//不太懂 } //關閉存儲數據的文件 for(int i = 0;i < 4;i++) { //判斷文件是否打開,若打開了關閉 if(m_waveDataFile[i].m_hFile != CFile::hFileNull) { m_waveDataFile[i].Close(); } } GetDlgItem(IDC_BUTTON_START)->SetWindowTextW(_T("開始工作")); return; } //------------打開設置---------------------// //設備類型 m_deviceType = VCI_USBCAN2; //設備索引號,只有一個設備,索引號為0 m_deviceIndex = 0; //第0路CAN--只有一路,用戶選擇 CString canNum; m_selectCANNum.GetWindowTextW(canNum); m_canNumA = _ttoi(canNum); if(VCI_OpenDevice(m_deviceType,m_deviceIndex,0) != STATUS_OK)//m_deviceType:設備類型號;m_deviceIndex:設備索引號;最后一個是保留參數,一般為0 { MessageBox(_T("打開設備失敗!",_T("警告"),MB_OK|MB_ICONQUESTION)); showListInfo(_T("打開設備失敗")); SetHScroll(); return ; } else { showListInfo(_T("打開設備成功")); SetHScroll(); } ///-------------對CAN進行初始化------------------// //對CAN進行初始化 VCI_INIT_CONFIG init_config; init_config.AccCode = 0x00000000; init_config.AccMask = 0xffffffff;//表示全部接收,(全部接收,AccMask:0xffffffff;AccCode:0x00000000---這塊可以通過測試軟件中的濾波設置功能中計算) init_config.Mode = 0;//正常模式;1:表示只聽模式(只接收,不影響總線) init_config.Timing0 = 0x00; init_config.Timing1 = 0x14;//相當於波特率1000kbps if(VCI_InitCAN(m_deviceType,m_deviceIndex,m_canNumA,&init_config) != STATUS_OK) { MessageBox(_T("初始化CAN失敗!"),_T("警告"),MB_OK|MB_ICONQUESTION); VCI_CloseDevice(m_deviceType,m_deviceIndex); showListInfo(_T("初始化CAN失敗")); SetHScroll(); return ; } else { showListInfo(_T("初始化CAN成功")); SetHScroll(); } m_connect = 1; GetDlgItem(IDC_BUTTON_CONNECT)->SetWindowTextW(_T("斷開")); //創建存儲數據的文件 CTime time0 = CTime::GetCurrentTime(); CString fileName = _T("WaveData"); if(!PathIsDirectory(fileName)) { ::CreateDirectory(fileName,NULL); } fileName.Format(_T("WaveData/%d-%d %dh%dm%ds"),time0.GetMonth(),time0.GetDay(),time0.GetHour(),time0.GetMinute(),time0.GetSecond()); if(!PathIsDirectory(fileName)) { ::CreateDirectory(fileName,NULL); } CString fileName0 = fileName; for(int i = 0;i < 4;i++) { CString i0; i0.Format(_T("/%dth"),i+1); fileName = fileName0 + i0; fileName += _T(".txt"); m_waveDataFile[i].Open(fileName,CFile::modeWrite|CFile::modeCreate|CFile::modeNoTruncate);//若文件存在,則清空 } //開啟接收數據的線程 m_pThread = AfxBeginThread(ReceiveThread,this,0,CREATE_SUSPENDED,NULL); m_pThread->m_bAutoDelete = false; }
2.2 接收數據
UINT CTest_OilDlg::ReceiveThread(void *param) { CTest_OilDlg *dlg = (CTest_OilDlg*)param; VCI_CAN_OBJ frameInfo[5000];//一次性從緩沖區獲取50個幀 VCI_ERR_INFO errInfo; int len = 1;//獲取到的CAN幀的個數 int i = 0; CString str,tmpstr; while(1) { Sleep(1); if(dlg->m_connect == 0) { break; } //獲取緩沖區的長度 int lenBuf = VCI_GetReceiveNum(dlg->m_deviceType,dlg->m_deviceIndex,dlg->m_canNumA); //獲取到的數據的個數,如果緩沖區大於5000,則取出5000,否則將緩沖區全部取出 len = VCI_Receive(dlg->m_deviceType,dlg->m_deviceIndex,dlg->m_canNumA,frameInfo,5000,400);//每次從緩沖區獲取50幀,等待200ms無響應后結束 if(len <= 0) { //注意:如果沒有讀到數據則必須調用此函數來讀取出當前的錯誤碼 //千萬不能省略這一步(即使你可能不想知道錯誤碼是什么) DWORD error = VCI_ReadErrInfo(dlg->m_deviceType,dlg->m_deviceIndex,dlg->m_canNumA,&errInfo);//返回值為1 表示操作成功 if((errInfo.ErrCode & 0x0000) == 0x0000) { //表示錯誤碼是0x0000 } } else { for(i = 0;i < len;i++) { str = _T("數據:\n"); if(frameInfo[i].DataLen > 8) frameInfo[i].DataLen = 8; //原始數據----但是這里沒有保存 for(int j = 0; j < frameInfo[i].DataLen;j++) { tmpstr.Format(_T("%04x \n"),frameInfo[i].Data[j]); str += tmpstr; } ::SendMessage(dlg->GetSafeHwnd(),WM_WAVEFORM,WPARAM(&frameInfo[i]),NULL); //TRACE(_T("receive\n")); } } } return 0; }
這里的數據處理是通過發送自定義消息的方法實現的,因為這些數據同時也要畫成曲線顯示在界面上,需要對界面進行更新操作,這時候需要給界面的主線程發消息去實現界面更新
2.3 發送數據

void CTest_OilDlg::OnBnClickedButtonSend() { //-----------------發送井下儀器工作模式命令-------------------// if(m_connect == 0) return ; VCI_CAN_OBJ frameInfo; //設置發送重發超時時間,建議不小於1500ms,默認4000ms VCI_SetReference(m_deviceType,m_deviceIndex,m_canNumA,4,&m_sendTimeout); frameInfo.ID = 0x84444444;//需要再確定 frameInfo.SendType = 0;//正常發送 frameInfo.RemoteFlag = 0;//數據幀 frameInfo.ExternFlag = 1;//擴展幀 frameInfo.DataLen = 3;//一個字節 frameInfo.Data[0] = 0x04; frameInfo.Data[1] = 0xff; //01儀器待機;02:儀器自檢;03:儀器定時開關機;04:儀器測試;05:儀器連續工作 frameInfo.Data[2] = m_selectMode.GetCurSel() + 1; int ret = VCI_Transmit(m_deviceType,m_deviceIndex,m_canNumA,&frameInfo,1); if(ret == 1) { showListInfo(_T("命令發送成功")); SetHScroll(); } else { showListInfo(_T("命令發送失敗")); SetHScroll(); } }
3、問題
做到現在,程序自發自收可以,接收下位機數據能接受5個左右的循環就接不到了,后來把數據的操作都屏蔽掉,只接收,發現也接不到,緩沖區內的數據個數為0.這個問題還沒解決。
這個問題在使用USBCAN給的例程里面也是存在的,目前不清楚什么問題
3.28:這個問題是因為下位機需要跟兩個CAN總線交互,一個是跟上位機,一個是跟其他板子,跟其他板子交互的CAN必須要有人接收,他才會持續的給上位機發數,上位機收不到是因為下位機的另一個CAN沒有接收,進入了中斷。而CANtest能接收是因為,我在操作的時候犯懶,同時打開了兩個通道,也就是另一個通道有人接收,所以才能持續發數。