一、项目结构
CANMonitor/
├── CANMonitor.dsp // VC6.0项目文件
├── CANMonitor.dsw // VC6.0工作空间文件
├── CANMonitor.cpp // 主程序
├── CANMonitor.h // 主头文件
├── CANMonitor.rc // 资源文件
├── Resource.h // 资源定义
├── CANDevice.h // CAN设备接口
├── CANDevice.cpp // CAN设备实现
└── CANMonitorDlg.h/cpp // 主对话框
二、主要功能模块
1. CANDevice.h - CAN设备接口类
cpp
// CANDevice.h
#ifndef __CANDEVICE_H__
#define __CANDEVICE_H__
// CAN消息结构
typedef struct _CAN_MSG
{
DWORD dwID; // 报文ID
BYTE bData[8]; // 数据
BYTE bLen; // 数据长度
BYTE bExternFlag; // 扩展帧标志
BYTE bRemoteFlag; // 远程帧标志
DWORD dwTimeStamp; // 时间戳
} CAN_MSG, *PCAN_MSG;
// CAN设备基类
class CCANDevice
{
public:
CCANDevice();
virtual ~CCANDevice();
// 设备操作
virtual BOOL OpenDevice(DWORD dwDeviceType, DWORD dwDeviceIndex, DWORD dwCanIndex) = 0;
virtual BOOL CloseDevice() = 0;
virtual BOOL InitCan(DWORD dwBaudRate) = 0;
virtual BOOL StartCan() = 0;
virtual BOOL StopCan() = 0;
// 数据收发
virtual BOOL SendMessage(PCAN_MSG pMsg) = 0;
virtual INT ReceiveMessage(PCAN_MSG pMsg, INT nMaxNum, INT nWaitTime = 0) = 0;
// 过滤设置
virtual BOOL SetFilter(DWORD dwCode, DWORD dwMask) = 0;
// 获取设备信息
virtual CString GetDeviceInfo() = 0;
// 错误信息
CString GetLastError() { return m_strLastError; }
protected:
CString m_strLastError;
BOOL m_bDeviceOpen;
BOOL m_bCanStart;
};
#endif // __CANDEVICE_H__
2. 主对话框头文件 - CANMonitorDlg.h
cpp
// CANMonitorDlg.h
class CCANMonitorDlg : public CDialog
{
public:
CCANMonitorDlg(CWnd* pParent = NULL);
enum { IDD = IDD_CANMONITOR_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
DECLARE_MESSAGE_MAP()
public:
// 控件变量
CComboBox m_cmbDeviceType;
CComboBox m_cmbDeviceIndex;
CComboBox m_cmbCanIndex;
CComboBox m_cmbBaudRate;
CEdit m_editSendID;
CEdit m_editSendData;
CComboBox m_cmbDataLen;
CComboBox m_cmbFrameType;
CListCtrl m_listReceive;
CButton m_btnOpen;
CButton m_btnSend;
CButton m_btnClear;
CRichEditCtrl m_editLog;
// 状态栏
CStatusBar m_wndStatusBar;
// 设备对象
CCANDevice* m_pCANDevice;
// 线程控制
CWinThread* m_pRecvThread;
BOOL m_bThreadRun;
HANDLE m_hThreadEvent;
// 消息处理
afx_msg void OnBtnOpen();
afx_msg void OnBtnSend();
afx_msg void OnBtnClear();
afx_msg void OnBtnFilter();
afx_msg void OnTimer(UINT nIDEvent);
afx_msg void OnDestroy();
// 自定义消息
#define WM_CAN_RECEIVE (WM_USER + 100)
afx_msg LRESULT OnCanReceive(WPARAM wParam, LPARAM lParam);
// 辅助函数
void AddLog(LPCTSTR lpszFormat, ...);
void UpdateDeviceList();
void StartReceiveThread();
void StopReceiveThread();
static UINT ReceiveThread(LPVOID pParam);
// 数据解析
CString FormatCanMsg(PCAN_MSG pMsg);
void DisplayCanMsg(PCAN_MSG pMsg);
};
3. 主对话框实现 - CANMonitorDlg.cpp(部分关键代码)
cpp
// CANMonitorDlg.cpp
#include "stdafx.h"
#include "CANMonitor.h"
#include "CANMonitorDlg.h"
#include "CANDevice.h"
#include "ZLGCanDevice.h" // 示例:周立功CAN卡实现
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
// 初始化对话框
BOOL CCANMonitorDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// 设置图标
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
// 初始化控件
InitControls();
// 创建设备对象
m_pCANDevice = new CZLGCanDevice(); // 使用周立功CAN卡
// 初始化状态栏
m_wndStatusBar.Create(WS_CHILD | WS_VISIBLE, CRect(0,0,0,0), this, IDC_STATUS_BAR);
m_wndStatusBar.SetIndicators(NULL, 2);
m_wndStatusBar.SetPaneInfo(0, IDS_STATUS_READY, SBPS_NORMAL, 200);
m_wndStatusBar.SetPaneInfo(1, IDS_STATUS_CONNECT, SBPS_NORMAL, 100);
// 初始化线程事件
m_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
m_bThreadRun = FALSE;
// 启动定时器更新状态
SetTimer(1, 1000, NULL);
return TRUE;
}
// 打开/关闭设备
void CCANMonitorDlg::OnBtnOpen()
{
CString strText;
m_btnOpen.GetWindowText(strText);
if (strText == _T("打开设备"))
{
// 获取设备参数
DWORD dwDeviceType = m_cmbDeviceType.GetCurSel();
DWORD dwDeviceIndex = m_cmbDeviceIndex.GetCurSel();
DWORD dwCanIndex = m_cmbCanIndex.GetCurSel();
DWORD dwBaudRate = GetBaudRateValue(m_cmbBaudRate.GetCurSel());
// 打开设备
if (m_pCANDevice->OpenDevice(dwDeviceType, dwDeviceIndex, dwCanIndex))
{
if (m_pCANDevice->InitCan(dwBaudRate))
{
if (m_pCANDevice->StartCan())
{
m_btnOpen.SetWindowText(_T("关闭设备"));
m_btnSend.EnableWindow(TRUE);
AddLog(_T("设备打开成功,波特率:%d bps"), dwBaudRate);
// 启动接收线程
StartReceiveThread();
}
}
}
else
{
AfxMessageBox(m_pCANDevice->GetLastError());
}
}
else
{
// 关闭设备
StopReceiveThread();
m_pCANDevice->StopCan();
m_pCANDevice->CloseDevice();
m_btnOpen.SetWindowText(_T("打开设备"));
m_btnSend.EnableWindow(FALSE);
AddLog(_T("设备已关闭"));
}
}
// 发送CAN消息
void CCANMonitorDlg::OnBtnSend()
{
CString strID, strData;
m_editSendID.GetWindowText(strID);
m_editSendData.GetWindowText(strData);
if (strID.IsEmpty())
{
AfxMessageBox(_T("请输入报文ID"));
return;
}
// 创建CAN消息
CAN_MSG canMsg;
memset(&canMsg, 0, sizeof(canMsg));
// 解析ID
canMsg.dwID = strtoul(strID, NULL, 16);
// 设置帧类型
int nFrameType = m_cmbFrameType.GetCurSel();
canMsg.bExternFlag = (nFrameType == 1) ? 1 : 0; // 扩展帧
// 设置数据长度
canMsg.bLen = (BYTE)m_cmbDataLen.GetCurSel();
// 解析数据
CString strTemp = strData;
strTemp.Remove(' ');
for (int i = 0; i < canMsg.bLen && i < 8; i++)
{
CString strByte = strTemp.Mid(i * 2, 2);
canMsg.bData[i] = (BYTE)strtoul(strByte, NULL, 16);
}
// 发送消息
if (m_pCANDevice->SendMessage(&canMsg))
{
AddLog(_T("发送成功: ID=0x%X, Data=%s"), canMsg.dwID, FormatCanData(&canMsg));
}
else
{
AddLog(_T("发送失败: %s"), m_pCANDevice->GetLastError());
}
}
// 接收线程函数
UINT CCANMonitorDlg::ReceiveThread(LPVOID pParam)
{
CCANMonitorDlg* pDlg = (CCANMonitorDlg*)pParam;
CAN_MSG canMsgs[100];
int nCount;
while (pDlg->m_bThreadRun)
{
// 接收消息
nCount = pDlg->m_pCANDevice->ReceiveMessage(canMsgs, 100, 100);
if (nCount > 0)
{
// 发送消息到主窗口显示
for (int i = 0; i < nCount; i++)
{
::PostMessage(pDlg->m_hWnd, WM_CAN_RECEIVE,
(WPARAM)new CAN_MSG(canMsgs[i]), 0);
}
}
// 检查线程退出事件
if (WaitForSingleObject(pDlg->m_hThreadEvent, 0) == WAIT_OBJECT_0)
break;
}
return 0;
}
// 处理接收到的消息
LRESULT CCANMonitorDlg::OnCanReceive(WPARAM wParam, LPARAM lParam)
{
PCAN_MSG pMsg = (PCAN_MSG)wParam;
if (pMsg)
{
DisplayCanMsg(pMsg);
delete pMsg;
}
return 0;
}
// 格式化显示CAN消息
void CCANMonitorDlg::DisplayCanMsg(PCAN_MSG pMsg)
{
CString strTime, strID, strData, strType, strLen;
int nIndex = m_listReceive.GetItemCount();
// 时间
CTime time = CTime::GetCurrentTime();
strTime = time.Format(_T("%H:%M:%S"));
// ID
if (pMsg->bExternFlag)
strID.Format(_T("%08X"), pMsg->dwID);
else
strID.Format(_T("%03X"), pMsg->dwID);
// 数据
for (int i = 0; i < pMsg->bLen; i++)
{
CString strByte;
strByte.Format(_T("%02X "), pMsg->bData[i]);
strData += strByte;
}
// 类型
strType = pMsg->bRemoteFlag ? _T("远程帧") : _T("数据帧");
// 添加到列表
m_listReceive.InsertItem(nIndex, strTime);
m_listReceive.SetItemText(nIndex, 1, strID);
m_listReceive.SetItemText(nIndex, 2, strData);
m_listReceive.SetItemText(nIndex, 3, strType);
m_listReceive.SetItemText(nIndex, 4, strLen);
// 自动滚动
m_listReceive.EnsureVisible(nIndex, FALSE);
}
4. 资源文件 - CANMonitor.rc(部分)
rc
// 对话框资源
IDD_CANMONITOR_DIALOG DIALOGEX 0, 0, 800, 600
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "CAN总线监控工具"
FONT 9, "宋体"
BEGIN
// 设备参数组
GROUPBOX "设备参数", IDC_STATIC, 10, 10, 350, 120
LTEXT "设备类型:", IDC_STATIC, 20, 30, 50, 20
COMBOBOX IDC_CMB_DEVICE_TYPE, 80, 30, 100, 100, CBS_DROPDOWNLIST | WS_TABSTOP
LTEXT "设备索引:", IDC_STATIC, 190, 30, 50, 20
COMBOBOX IDC_CMB_DEVICE_INDEX, 250, 30, 50, 100, CBS_DROPDOWNLIST | WS_TABSTOP
LTEXT "CAN通道:", IDC_STATIC, 310, 30, 50, 20
COMBOBOX IDC_CMB_CAN_INDEX, 370, 30, 50, 100, CBS_DROPDOWNLIST | WS_TABSTOP
LTEXT "波特率:", IDC_STATIC, 20, 60, 50, 20
COMBOBOX IDC_CMB_BAUD_RATE, 80, 60, 100, 100, CBS_DROPDOWNLIST | WS_TABSTOP
PUSHBUTTON "打开设备", IDC_BTN_OPEN, 200, 60, 80, 25
// 发送区
GROUPBOX "发送区", IDC_STATIC, 10, 140, 350, 150
LTEXT "报文ID:", IDC_STATIC, 20, 160, 50, 20
EDITEXT IDC_EDIT_SEND_ID, 80, 160, 100, 20, ES_AUTOHSCROLL
LTEXT "数据:", IDC_STATIC, 20, 190, 50, 20
EDITEXT IDC_EDIT_SEND_DATA, 80, 190, 200, 20, ES_AUTOHSCROLL
LTEXT "长度:", IDC_STATIC, 20, 220, 50, 20
COMBOBOX IDC_CMB_DATA_LEN, 80, 220, 50, 100, CBS_DROPDOWNLIST
LTEXT "帧类型:", IDC_STATIC, 150, 220, 50, 20
COMBOBOX IDC_CMB_FRAME_TYPE, 200, 220, 80, 100, CBS_DROPDOWNLIST
PUSHBUTTON "发送", IDC_BTN_SEND, 20, 250, 80, 25
// 接收区
GROUPBOX "接收区", IDC_STATIC, 370, 10, 420, 450
CONTROL "", IDC_LIST_RECEIVE, "SysListView32", LVS_REPORT | WS_BORDER | WS_TABSTOP,
380, 30, 400, 350
PUSHBUTTON "清空", IDC_BTN_CLEAR, 380, 390, 80, 25
PUSHBUTTON "过滤设置", IDC_BTN_FILTER, 500, 390, 80, 25
// 日志区
GROUPBOX "日志", IDC_STATIC, 10, 300, 780, 200
CONTROL "", IDC_EDIT_LOG, "RichEdit20A", ES_MULTILINE | WS_BORDER | WS_VSCROLL | ES_AUTOVSCROLL,
20, 320, 760, 170
END
三、使用说明
1. 编译环境配置
cpp
// 在VC6.0中配置:
// 1. 创建MFC对话框应用程序
// 2. 添加必要的头文件和库文件路径
// 3. 链接对应的CAN卡厂商提供的库文件
// 4. 设置字符集为多字节字符集
2. 支持的CAN卡类型
cpp
// 可以根据需要实现不同厂商的CAN卡驱动
class CZLGCanDevice : public CCANDevice // 周立功CAN卡
{
// 实现具体接口
};
class CPCICanDevice : public CCANDevice // PCI CAN卡
{
// 实现具体接口
};
class CUSBCanDevice : public CCANDevice // USB CAN卡
{
// 实现具体接口
};
3. 主要功能特点
- 支持多种CAN卡设备
- 实时接收和显示CAN数据
- 支持标准帧和扩展帧
- 可配置波特率
- 数据过滤功能
- 日志记录
- 数据保存和导出
参考代码 CAN上位机,实现CAN通讯基本功能 www.youwenfan.com/contentcsv/71943.html
四、注意事项
- 兼容性:VC6.0相对较老,注意API的兼容性
- 库文件:需要对应的CAN卡厂商提供的SDK
- 多线程:接收线程要注意线程安全
- 性能:大数据量时注意界面刷新优化
- 错误处理:添加充分的错误处理机制