基于VC6.0的CAN上位机程序,实现基本的CAN通信功能

一、项目结构

复制代码
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

四、注意事项

  1. 兼容性:VC6.0相对较老,注意API的兼容性
  2. 库文件:需要对应的CAN卡厂商提供的SDK
  3. 多线程:接收线程要注意线程安全
  4. 性能:大数据量时注意界面刷新优化
  5. 错误处理:添加充分的错误处理机制
相关推荐
study_小达人2 天前
VMware 网络相关
vmware·vc
RevsInterstellar4 个月前
在ATL中加入带SAFEARRAY参数的方法
组件·com·vc·atl·safearray·com组件·com库
老吴学AI4 个月前
系列报告十三:(MTB)Physical AI: Shaping the Market of the New Possible — 2025 Report
大数据·人工智能·具身智能·vc·投融资
Joy-鬼魅5 个月前
Win10x64系统VS2022使用CreateFileMapping返回无效句柄
c++·createfilemap·vc·getlasterror
Joy-鬼魅5 个月前
VC中共享内存的命名空间
c++·vc·共享内存命名空间
Joy-鬼魅5 个月前
vs调试器设置解决创建共享内存返回错误码5的问题
c++·microsoft·createfilemap·vc
丁劲犇5 个月前
Visual C++下使用Win32 API为Release模式导出崩溃堆栈
c++·windows·crash·dump·离线调试·vc·崩溃堆栈
xipxiks1 年前
Visual Components 自定义工具创建吸附接口
仿真·工业机器人·vc·pnp·visual components·onetooneinterface
DoWeixin61 年前
【请关注】VC内存泄露的排除及处理
c++·mfc·vc++·vc