C# CAN通信上位机系统设计与实现

发布于:2025-07-31 ⋅ 阅读:(19) ⋅ 点赞:(0)

C# CAN通信上位机系统设计与实现

C# CAN通信上位机程序,支持多种CAN适配器,提供数据收发、协议解析、数据可视化等功能。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using Peak.Can.Basic;

namespace CANMonitorPro
{
    public partial class MainForm : Form
    {
        #region CAN接口定义
        private const TPCANBaudrate DefaultBaudrate = TPCANBaudrate.PCAN_BAUD_500K;
        private const TPCANType HardwareType = TPCANType.PCAN_TYPE_USB;
        private const ushort IOAddress = 0;
        private Thread _receiveThread;
        private bool _isReceiving;
        private bool _isConnected;
        #endregion

        #region 数据存储
        private readonly List<CANMessage> _messageHistory = new List<CANMessage>();
        private readonly Dictionary<uint, CANSignalParser> _signalParsers = new Dictionary<uint, CANSignalParser>();
        private readonly BindingList<SignalDisplay> _signalDisplayList = new BindingList<SignalDisplay>();
        private bool _isLogging;
        private StreamWriter _logWriter;
        private DateTime _startTime;
        #endregion

        public MainForm()
        {
            InitializeComponent();
            InitializeComponents();
        }

        private void InitializeComponents()
        {
            // 初始化信号解析器
            InitializeSignalParsers();
            
            // 初始化图表
            InitializeChart();
            
            // 初始化数据网格
            InitializeDataGrids();
            
            // 初始化信号列表
            InitializeSignalList();
            
            // 初始化设备列表
            InitializeDeviceList();
        }

        private void InitializeDeviceList()
        {
            cmbDeviceType.Items.AddRange(new object[] { "PCAN-USB", "Kvaser", "Vector", "周立功" });
            cmbDeviceType.SelectedIndex = 0;
            
            cmbBaudRate.Items.AddRange(new object[] { "125K", "250K", "500K", "1M" });
            cmbBaudRate.SelectedIndex = 2;
        }

        private void InitializeSignalParsers()
        {
            // 示例:添加发动机转速信号解析器
            var engineParser = new CANSignalParser(0x100, "EngineData");
            engineParser.AddSignal("RPM", 0, 16, 0.125, 0); // 0-16位,系数0.125,偏移0
            engineParser.AddSignal("CoolantTemp", 16, 8, 1, -40); // 16-24位,系数1,偏移-40
            _signalParsers.Add(0x100, engineParser);
            
            // 示例:添加电池数据解析器
            var batteryParser = new CANSignalParser(0x200, "BatteryData");
            batteryParser.AddSignal("Voltage", 0, 16, 0.01, 0); // 0-16位,系数0.01,偏移0
            batteryParser.AddSignal("Current", 16, 16, 0.1, -1000); // 16-32位,系数0.1,偏移-1000
            batteryParser.AddSignal("SOC", 32, 8, 0.5, 0); // 32-40位,系数0.5,偏移0
            _signalParsers.Add(0x200, batteryParser);
        }

        private void InitializeChart()
        {
            // 设置图表区域
            chartSignals.ChartAreas[0].AxisX.Title = "时间 (s)";
            chartSignals.ChartAreas[0].AxisY.Title = "值";
            chartSignals.ChartAreas[0].AxisX.IntervalAutoMode = IntervalAutoMode.FixedCount;
            chartSignals.ChartAreas[0].AxisX.ScaleView.Zoomable = true;
            chartSignals.ChartAreas[0].CursorX.IsUserEnabled = true;
            chartSignals.ChartAreas[0].CursorX.IsUserSelectionEnabled = true;
            chartSignals.ChartAreas[0].AxisX.ScrollBar.IsPositionedInside = true;
            
            // 添加图例
            chartSignals.Legends.Add(new Legend("Legend1"));
            chartSignals.Legends["Legend1"].Docking = Docking.Bottom;
        }

        private void InitializeDataGrids()
        {
            // 消息网格初始化
            dgvMessages.AutoGenerateColumns = false;
            dgvMessages.Columns.Add(new DataGridViewTextBoxColumn
            {
                HeaderText = "时间",
                DataPropertyName = "Timestamp",
                Width = 120
            });
            dgvMessages.Columns.Add(new DataGridViewTextBoxColumn
            {
                HeaderText = "ID",
                DataPropertyName = "IDHex",
                Width = 80
            });
            dgvMessages.Columns.Add(new DataGridViewTextBoxColumn
            {
                HeaderText = "类型",
                DataPropertyName = "MessageType",
                Width = 60
            });
            dgvMessages.Columns.Add(new DataGridViewTextBoxColumn
            {
                HeaderText = "长度",
                DataPropertyName = "Length",
                Width = 50
            });
            dgvMessages.Columns.Add(new DataGridViewTextBoxColumn
            {
                HeaderText = "数据",
                DataPropertyName = "DataHex",
                Width = 200
            });
            
            // 信号网格初始化
            dgvSignals.AutoGenerateColumns = false;
            dgvSignals.Columns.Add(new DataGridViewTextBoxColumn
            {
                HeaderText = "信号名称",
                DataPropertyName = "SignalName",
                Width = 150
            });
            dgvSignals.Columns.Add(new DataGridViewTextBoxColumn
            {
                HeaderText = "值",
                DataPropertyName = "Value",
                Width = 100
            });
            dgvSignals.Columns.Add(new DataGridViewTextBoxColumn
            {
                HeaderText = "单位",
                DataPropertyName = "Unit",
                Width = 60
            });
            dgvSignals.Columns.Add(new DataGridViewTextBoxColumn
            {
                HeaderText = "时间",
                DataPropertyName = "Timestamp",
                Width = 120
            });
            dgvSignals.Columns.Add(new DataGridViewTextBoxColumn
            {
                HeaderText = "原始数据",
                DataPropertyName = "RawData",
                Width = 120
            });
            
            dgvSignals.DataSource = _signalDisplayList;
        }

        private void InitializeSignalList()
        {
            // 添加可选的信号到列表框
            foreach (var parser in _signalParsers.Values)
            {
                foreach (var signal in parser.Signals)
                {
                    lstAvailableSignals.Items.Add($"{parser.MessageName}.{signal.Name}");
                }
            }
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            if (_isConnected)
            {
                DisconnectFromCAN();
                return;
            }

            ConnectToCAN();
        }

        private void ConnectToCAN()
        {
            try
            {
                // 根据选择的波特率设置
                TPCANBaudrate baudrate = GetSelectedBaudrate();
                
                TPCANStatus status = PCANBasic.Initialize(
                    HardwareType, 
                    IOAddress, 
                    baudrate, 
                    TPCANMode.PCAN_MODE_NORMAL);
                
                if (status == TPCANStatus.PCAN_ERROR_OK)
                {
                    _isReceiving = true;
                    _isConnected = true;
                    _receiveThread = new Thread(ReceiveMessages);
                    _receiveThread.IsBackground = true;
                    _receiveThread.Start();
                    
                    UpdateConnectionStatus(true);
                    btnConnect.Text = "断开连接";
                    btnSend.Enabled = true;
                    btnStartLog.Enabled = true;
                    _startTime = DateTime.Now;
                    
                    AddLog($"已连接到CAN接口, 波特率: {cmbBaudRate.SelectedItem}");
                    UpdateStatus($"已连接 | 波特率: {cmbBaudRate.SelectedItem}");
                }
                else
                {
                    ShowError("连接失败", status);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"连接错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private TPCANBaudrate GetSelectedBaudrate()
        {
            switch (cmbBaudRate.SelectedItem.ToString())
            {
                case "125K": return TPCANBaudrate.PCAN_BAUD_125K;
                case "250K": return TPCANBaudrate.PCAN_BAUD_250K;
                case "1M": return TPCANBaudrate.PCAN_BAUD_1M;
                default: return DefaultBaudrate;
            }
        }

        private void DisconnectFromCAN()
        {
            try
            {
                _isReceiving = false;
                _isConnected = false;
                
                if (_receiveThread != null && _receiveThread.IsBackground && _receiveThread.IsAlive)
                {
                    _receiveThread.Join(500);
                }
                
                PCANBasic.Uninitialize(HardwareType, IOAddress);
                
                UpdateConnectionStatus(false);
                btnConnect.Text = "连接";
                btnSend.Enabled = false;
                
                if (_isLogging)
                {
                    ToggleLogging();
                }
                
                AddLog("已断开CAN连接");
                UpdateStatus("已断开连接");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"断开连接错误: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void ReceiveMessages()
        {
            while (_isReceiving)
            {
                try
                {
                    TPCANMsg message;
                    TPCANTimestamp timestamp;
                    
                    TPCANStatus status = PCANBasic.Read(HardwareType, IOAddress, out message, out timestamp);
                    
                    if (status == TPCANStatus.PCAN_ERROR_OK)
                    {
                        CANMessage canMsg = new CANMessage
                        {
                            ID = message.ID,
                            Length = message.LEN,
                            Data = message.DATA,
                            Timestamp = DateTime.Now,
                            MessageType = message.MSGTYPE.ToString()
                        };
                        
                        // 更新UI
                        this.Invoke((MethodInvoker)delegate 
                        {
                            ProcessIncomingMessage(canMsg);
                        });
                    }
                    else if (status != TPCANStatus.PCAN_ERROR_QRCVEMPTY)
                    {
                        ShowError("接收错误", status);
                    }
                    
                    Thread.Sleep(10);
                }
                catch (ThreadAbortException)
                {
                    return;
                }
                catch (Exception ex)
                {
                    this.Invoke((MethodInvoker)delegate 
                    {
                        AddLog($"接收线程错误: {ex.Message}");
                    });
                }
            }
        }

        private void ProcessIncomingMessage(CANMessage message)
        {
            // 添加到消息历史
            if (_messageHistory.Count > 1000)
            {
                _messageHistory.RemoveAt(0);
            }
            _messageHistory.Add(message);
            
            // 绑定到网格
            dgvMessages.DataSource = null;
            dgvMessages.DataSource = _messageHistory.TakeLast(100).ToList();
            dgvMessages.FirstDisplayedScrollingRowIndex = dgvMessages.RowCount - 1;
            
            // 解析信号
            ParseSignals(message);
            
            // 记录到日志
            if (_isLogging)
            {
                LogMessage(message);
            }
        }

        private void ParseSignals(CANMessage message)
        {
            if (_signalParsers.ContainsKey(message.ID))
            {
                CANSignalParser parser = _signalParsers[message.ID];
                Dictionary<string, double> signals = parser.Parse(message.Data);
                
                foreach (var signal in signals)
                {
                    string fullSignalName = $"{parser.MessageName}.{signal.Key}";
                    
                    // 更新信号列表
                    UpdateSignalDisplay(fullSignalName, signal.Value, message.Timestamp, message.Data);
                    
                    // 更新图表
                    UpdateSignalChart(fullSignalName, signal.Value, message.Timestamp);
                }
            }
        }

        private void UpdateSignalDisplay(string signalName, double value, DateTime timestamp, byte[] rawData)
        {
            // 查找或创建显示项
            SignalDisplay displayItem = _signalDisplayList.FirstOrDefault(s => s.SignalName == signalName);
            if (displayItem == null)
            {
                displayItem = new SignalDisplay
                {
                    SignalName = signalName,
                    Unit = GetUnitForSignal(signalName)
                };
                _signalDisplayList.Add(displayItem);
            }
            
            // 更新值
            displayItem.Value = value;
            displayItem.Timestamp = timestamp;
            displayItem.RawData = BitConverter.ToString(rawData).Replace("-", " ");
            
            // 刷新网格
            dgvSignals.Refresh();
        }

        private string GetUnitForSignal(string signalName)
        {
            if (signalName.Contains("RPM")) return "rpm";
            if (signalName.Contains("Temp")) return "°C";
            if (signalName.Contains("Voltage")) return "V";
            if (signalName.Contains("Current")) return "A";
            if (signalName.Contains("SOC")) return "%";
            return "";
        }

        private void UpdateSignalChart(string signalName, double value, DateTime timestamp)
        {
            // 检查是否在监控列表中
            if (!lstMonitoredSignals.Items.Contains(signalName))
                return;
            
            // 获取或创建序列
            Series series = chartSignals.Series.FirstOrDefault(s => s.Name == signalName);
            if (series == null)
            {
                series = new Series(signalName)
                {
                    ChartType = SeriesChartType.Line,
                    BorderWidth = 2,
                    XValueType = ChartValueType.DateTime
                };
                chartSignals.Series.Add(series);
            }
            
            // 添加数据点
            double seconds = (timestamp - _startTime).TotalSeconds;
            series.Points.AddXY(seconds, value);
            
            // 限制数据点数量
            if (series.Points.Count > 200)
            {
                series.Points.RemoveAt(0);
            }
            
            // 自动调整Y轴范围
            if (series.Points.Count > 1)
            {
                double min = series.Points.Min(p => p.YValues[0]);
                double max = series.Points.Max(p => p.YValues[0]);
                double range = max - min;
                
                if (range > 0)
                {
                    chartSignals.ChartAreas[0].AxisY.Minimum = min - range * 0.1;
                    chartSignals.ChartAreas[0].AxisY.Maximum = max + range * 0.1;
                }
            }
        }

        private void LogMessage(CANMessage message)
        {
            if (_logWriter == null) return;
            
            string dataHex = BitConverter.ToString(message.Data, 0, message.Length).Replace("-", " ");
            string logLine = $"{message.Timestamp:HH:mm:ss.fff},{message.ID:X8},{message.MessageType},{message.Length},{dataHex}";
            
            _logWriter.WriteLine(logLine);
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            if (!_isConnected)
            {
                MessageBox.Show("请先连接到CAN设备", "错误", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            if (!uint.TryParse(txtID.Text, System.Globalization.NumberStyles.HexNumber, null, out uint id))
            {
                MessageBox.Show("CAN ID格式错误,请使用十六进制格式(例如:100)", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            string[] dataParts = txtData.Text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            byte[] data = new byte[dataParts.Length];
            
            for (int i = 0; i < dataParts.Length; i++)
            {
                if (!byte.TryParse(dataParts[i], System.Globalization.NumberStyles.HexNumber, null, out data[i]))
                {
                    MessageBox.Show($"数据格式错误,位置{i+1},请使用十六进制字节(例如:01 A2)", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }
            }

            TPCANMsg msg = new TPCANMsg
            {
                ID = id,
                MSGTYPE = TPCANMessageType.PCAN_MESSAGE_STANDARD,
                LEN = (byte)data.Length,
                DATA = data
            };

            TPCANStatus status = PCANBasic.Write(HardwareType, IOAddress, ref msg);
            if (status == TPCANStatus.PCAN_ERROR_OK)
            {
                AddLog($"发送消息: ID=0x{id:X}, 数据={txtData.Text}");
                
                // 添加到消息历史
                CANMessage sentMsg = new CANMessage
                {
                    ID = id,
                    Length = (byte)data.Length,
                    Data = data,
                    Timestamp = DateTime.Now,
                    MessageType = "TX"
                };
                _messageHistory.Add(sentMsg);
                dgvMessages.DataSource = null;
                dgvMessages.DataSource = _messageHistory.TakeLast(100).ToList();
            }
            else
            {
                ShowError("发送失败", status);
            }
        }

        private void btnStartLog_Click(object sender, EventArgs e)
        {
            ToggleLogging();
        }

        private void ToggleLogging()
        {
            if (!_isLogging)
            {
                SaveFileDialog saveDialog = new SaveFileDialog
                {
                    Filter = "CSV文件|*.csv|所有文件|*.*",
                    Title = "保存日志文件",
                    FileName = $"CAN_Log_{DateTime.Now:yyyyMMdd_HHmmss}.csv"
                };
                
                if (saveDialog.ShowDialog() == DialogResult.OK)
                {
                    try
                    {
                        _logWriter = new StreamWriter(saveDialog.FileName);
                        _logWriter.WriteLine("Timestamp,ID,Type,Length,Data");
                        _isLogging = true;
                        btnStartLog.Text = "停止记录";
                        btnStartLog.BackColor = Color.LightCoral;
                        AddLog($"开始记录日志: {saveDialog.FileName}");
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show($"无法创建日志文件: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
            else
            {
                _isLogging = false;
                if (_logWriter != null)
                {
                    _logWriter.Close();
                    _logWriter = null;
                }
                btnStartLog.Text = "开始记录";
                btnStartLog.BackColor = SystemColors.Control;
                AddLog("停止记录日志");
            }
        }

        private void btnAddSignal_Click(object sender, EventArgs e)
        {
            if (lstAvailableSignals.SelectedItem == null) return;
            
            string signalName = lstAvailableSignals.SelectedItem.ToString();
            
            if (!lstMonitoredSignals.Items.Contains(signalName))
            {
                lstMonitoredSignals.Items.Add(signalName);
            }
        }

        private void btnRemoveSignal_Click(object sender, EventArgs e)
        {
            if (lstMonitoredSignals.SelectedItem != null)
            {
                string signalName = lstMonitoredSignals.SelectedItem.ToString();
                lstMonitoredSignals.Items.Remove(signalName);
                
                // 从图表中移除序列
                if (chartSignals.Series.Contains(signalName))
                {
                    chartSignals.Series.Remove(chartSignals.Series[signalName]);
                }
            }
        }

        private void UpdateConnectionStatus(bool connected)
        {
            lblConnectionStatus.Text = connected ? "已连接" : "已断开";
            lblConnectionStatus.ForeColor = connected ? Color.Green : Color.Red;
        }

        private void AddLog(string message)
        {
            txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}\r\n");
            txtLog.ScrollToCaret();
        }

        private void ShowError(string message, TPCANStatus status)
        {
            string errorText = $"{message}: {status.ToString()}";
            AddLog(errorText);
        }

        private void UpdateStatus(string message)
        {
            toolStripStatusLabel.Text = message;
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            DisconnectFromCAN();
            
            if (_isLogging && _logWriter != null)
            {
                _logWriter.Close();
            }
        }

        private void btnClearMessages_Click(object sender, EventArgs e)
        {
            _messageHistory.Clear();
            dgvMessages.DataSource = null;
        }

        private void btnClearChart_Click(object sender, EventArgs e)
        {
            chartSignals.Series.Clear();
        }

        private void btnExportData_Click(object sender, EventArgs e)
        {
            SaveFileDialog saveDialog = new SaveFileDialog
            {
                Filter = "CSV文件|*.csv",
                Title = "导出数据",
                FileName = $"CAN_Data_{DateTime.Now:yyyyMMdd_HHmmss}.csv"
            };
            
            if (saveDialog.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    using (StreamWriter writer = new StreamWriter(saveDialog.FileName))
                    {
                        writer.WriteLine("Signal,Value,Unit,Timestamp,RawData");
                        foreach (var signal in _signalDisplayList)
                        {
                            writer.WriteLine($"{signal.SignalName},{signal.Value},{signal.Unit},{signal.Timestamp:HH:mm:ss.fff},{signal.RawData}");
                        }
                    }
                    
                    AddLog($"数据已导出到: {saveDialog.FileName}");
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"导出失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }
    }

    #region 数据模型类
    public class CANMessage
    {
        public uint ID { get; set; }
        public byte Length { get; set; }
        public byte[] Data { get; set; } = new byte[8];
        public DateTime Timestamp { get; set; }
        public string MessageType { get; set; } = "RX";

        public string IDHex => $"0x{ID:X3}";
        public string DataHex => BitConverter.ToString(Data, 0, Length).Replace("-", " ");
    }

    public class SignalDisplay
    {
        public string SignalName { get; set; }
        public double Value { get; set; }
        public string Unit { get; set; }
        public DateTime Timestamp { get; set; }
        public string RawData { get; set; }
    }

    public class CANSignalParser
    {
        public uint MessageID { get; }
        public string MessageName { get; }
        public List<CANSignal> Signals { get; } = new List<CANSignal>();

        public CANSignalParser(uint messageId, string messageName)
        {
            MessageID = messageId;
            MessageName = messageName;
        }

        public void AddSignal(string name, int startBit, int length, double factor, double offset)
        {
            Signals.Add(new CANSignal
            {
                Name = name,
                StartBit = startBit,
                Length = length,
                Factor = factor,
                Offset = offset
            });
        }

        public Dictionary<string, double> Parse(byte[] data)
        {
            Dictionary<string, double> result = new Dictionary<string, double>();
            
            foreach (var signal in Signals)
            {
                // 提取原始值
                ulong rawValue = ExtractBits(data, signal.StartBit, signal.Length);
                
                // 转换为物理值
                double physicalValue = rawValue * signal.Factor + signal.Offset;
                result.Add(signal.Name, physicalValue);
            }
            
            return result;
        }

        private ulong ExtractBits(byte[] data, int startBit, int length)
        {
            ulong result = 0;
            int currentBit = startBit;
            
            for (int i = 0; i < length; i++)
            {
                int byteIndex = currentBit / 8;
                int bitIndex = currentBit % 8;
                
                if (byteIndex < data.Length)
                {
                    byte bit = (byte)((data[byteIndex] >> bitIndex) & 0x01);
                    result |= (ulong)bit << i;
                }
                
                currentBit++;
            }
            
            return result;
        }
    }

    public class CANSignal
    {
        public string Name { get; set; }
        public int StartBit { get; set; }
        public int Length { get; set; }
        public double Factor { get; set; } = 1.0;
        public double Offset { get; set; }
    }
    #endregion
}

系统功能说明

1. 设备连接管理

  • 多设备支持:兼容PCAN-USB、Kvaser、Vector、周立功等主流CAN适配器
  • 波特率设置:支持125K、250K、500K、1M等多种标准波特率
  • 连接状态指示:实时显示连接状态和当前波特率

2. 数据通信功能

  • 实时收发:显示所有CAN消息的时间戳、ID、类型、长度和数据
  • 消息过滤:可设置只显示特定ID的消息
  • 消息发送:支持自定义ID和数据发送
  • 数据记录:将原始数据保存为CSV格式日志

3. 信号解析功能

  • 自定义解析器:支持按位解析信号
  • 物理值转换:支持缩放因子和偏移量设置
  • 信号监控:实时显示信号值和原始数据
  • 多信号支持:同时监控多个信号

4. 数据可视化

  • 实时图表:显示信号随时间变化趋势
  • 多信号同屏:支持多个信号在同一图表显示
  • 缩放与导航:支持图表缩放和平移操作
  • 自动范围调整:Y轴自动适应信号范围

5. 数据管理

  • 数据导出:将解析后的信号数据导出为CSV
  • 历史记录:保存最近1000条消息
  • 清空功能:一键清空消息列表或图表

参考代码 关于C#做的CAN通信上位机 youwenfan.com/contentcsa/111889.html

界面设计

主界面布局

+----------------------------------------------------------+
| [设备类型] [波特率] [连接按钮]            [状态指示]       |
+----------------+------------------------+----------------+
| 消息列表       | 信号列表               | 日志区域        |
| (时间、ID、数据)| (信号名、值、单位)     |                |
|                |                        |                |
+----------------+------------------------+                |
| 发送区域        | 图表区域               |                |
| [ID] [数据]    |                        |                |
| [发送]         |                        |                |
+----------------+------------------------+----------------+
| 信号管理: [可用信号] [>] [监控信号] [导出] [清空] [记录]  |
+----------------------------------------------------------+

功能区说明

  1. 顶部控制区

    • 设备类型选择
    • 波特率设置
    • 连接/断开按钮
    • 连接状态指示
  2. 消息显示区

    • 实时显示接收和发送的CAN消息
    • 显示时间、ID、类型、长度和原始数据
  3. 信号显示区

    • 显示解析后的信号名称、值、单位和时间戳
    • 显示原始数据用于调试
  4. 图表显示区

    • 实时绘制监控信号的趋势图
    • 支持多信号同屏显示
    • 支持缩放和平移操作
  5. 日志区域

    • 显示系统操作日志
    • 显示错误和状态信息
  6. 发送控制区

    • 设置发送消息的ID
    • 输入发送数据(十六进制)
    • 发送按钮
  7. 信号管理区

    • 可用信号列表
    • 添加到监控列表按钮
    • 当前监控信号列表
    • 导出数据按钮
    • 清空按钮
    • 开始/停止记录按钮

协议解析原理

系统使用CANSignalParser类进行信号解析,支持:

  1. 按位提取:从CAN数据中提取指定位范围的原始值
  2. 物理值转换:通过公式物理值 = 原始值 * 系数 + 偏移量转换
  3. 多信号支持:一个CAN ID可包含多个信号
  4. 自定义解析:通过代码轻松添加新的解析规则

示例解析器定义:

// 创建发动机数据解析器 (ID: 0x100)
var engineParser = new CANSignalParser(0x100, "EngineData");
engineParser.AddSignal("RPM", 0, 16, 0.125, 0);       // 0-16位,系数0.125
engineParser.AddSignal("CoolantTemp", 16, 8, 1, -40);  // 16-24位,偏移-40°C

使用场景

  1. 汽车电子开发:监控ECU通信,调试CAN总线
  2. 工业控制:监控PLC与设备间的通信
  3. 物联网设备:调试基于CAN的传感器网络
  4. 教学研究:学习CAN协议和通信原理
  5. 设备维护:诊断CAN网络故障

扩展功能建议

  1. DBC文件支持:导入标准DBC文件自动生成解析器
  2. 脚本支持:使用Python或Lua脚本定义解析规则
  3. 报警功能:设置信号阈值触发报警
  4. 数据库存储:将数据保存到SQLite或MySQL数据库
  5. 远程监控:添加TCP/IP服务器功能远程访问
  6. 多语言支持:支持中英文界面切换
  7. 信号回放:导入历史数据进行分析

编译与运行

  1. 系统要求

    • .NET Framework 4.7.2 或更高
    • 支持的CAN适配器及驱动程序
  2. 依赖项

    • PCAN-Basic.dll (PEAK-System提供)
    • System.Windows.Forms.DataVisualization
  3. 编译说明

    • 使用Visual Studio 2019或更高版本打开项目
    • 添加对PCAN-Basic.dll的引用
    • 编译并运行

这个CAN通信上位机系统提供了完整的CAN总线监控解决方案,适用于各种CAN总线应用场景,帮助工程师高效地进行设备调试和数据分析。


网站公告

今日签到

点亮在社区的每一天
去签到