diff --git a/ADP.sln b/ADP.sln
index 4a7e18f..8114617 100644
--- a/ADP.sln
+++ b/ADP.sln
@@ -37,7 +37,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonitorModule", "MonitorMod
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceEditModule", "DeviceEditModule\DeviceEditModule.csproj", "{170AD4C1-189D-4FBE-B10D-2A4304527834}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "USBCANFD", "USBCANFD\USBCANFD.csproj", "{C011DC26-E2F8-4F40-AD62-C5D50BA56CE8}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZLGUSBCANFD", "ZLGUSBCANFD\ZLGUSBCANFD.csproj", "{542FE380-2344-4343-9AC6-F0C587C13B88}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -109,10 +109,10 @@ Global
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Debug|Any CPU.Build.0 = Debug|Any CPU
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Release|Any CPU.ActiveCfg = Release|Any CPU
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Release|Any CPU.Build.0 = Release|Any CPU
- {C011DC26-E2F8-4F40-AD62-C5D50BA56CE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C011DC26-E2F8-4F40-AD62-C5D50BA56CE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C011DC26-E2F8-4F40-AD62-C5D50BA56CE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C011DC26-E2F8-4F40-AD62-C5D50BA56CE8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {542FE380-2344-4343-9AC6-F0C587C13B88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {542FE380-2344-4343-9AC6-F0C587C13B88}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {542FE380-2344-4343-9AC6-F0C587C13B88}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {542FE380-2344-4343-9AC6-F0C587C13B88}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/ADP/ADP.csproj b/ADP/ADP.csproj
index 52c2fdc..40276bc 100644
--- a/ADP/ADP.csproj
+++ b/ADP/ADP.csproj
@@ -30,7 +30,7 @@
-
+
diff --git a/SettingModule/SettingModule.cs b/SettingModule/SettingModule.cs
index e4c3db0..a3f9aef 100644
--- a/SettingModule/SettingModule.cs
+++ b/SettingModule/SettingModule.cs
@@ -20,6 +20,7 @@ namespace SettingModule
// 设备连接配置弹窗
containerRegistry.RegisterDialog("TCPConfig");
containerRegistry.RegisterDialog("SerialPortConfig");
+ containerRegistry.RegisterDialog("CANConfig");
}
}
}
diff --git a/SettingModule/ViewModels/Dialogs/CANConfigViewModel.cs b/SettingModule/ViewModels/Dialogs/CANConfigViewModel.cs
new file mode 100644
index 0000000..5322528
--- /dev/null
+++ b/SettingModule/ViewModels/Dialogs/CANConfigViewModel.cs
@@ -0,0 +1,134 @@
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+using UIShare.PubEvent;
+using UIShare.UIViewModel;
+using UIShare.ViewModelBase;
+
+namespace SettingModule.ViewModels.Dialogs
+{
+ ///
+ /// CAN 连接配置对话框 VM。
+ /// 通过 DialogParameters 接收宿主 DeviceInfoVM;保存时把副本写回宿主。
+ /// 参数对应 ZLGCANFD 构造函数 + 初始化并启动通道 方法。
+ ///
+ public class CANConfigViewModel : DialogViewModelBase
+ {
+ #region 属性
+
+ private string _title = "CAN 连接配置";
+ public string Title
+ {
+ get => _title;
+ set => SetProperty(ref _title, value);
+ }
+
+ /// 编辑用的副本,取消时不会污染宿主对象。
+ private CANConfigVM _config = new();
+ public CANConfigVM Config
+ {
+ get => _config;
+ set => SetProperty(ref _config, value);
+ }
+
+ /// 常用仲裁域波特率。
+ public ObservableCollection CommonABitBauds { get; } = new()
+ {
+ "250000", "500000", "1000000"
+ };
+
+ /// 常用数据域波特率。
+ public ObservableCollection CommonDBitBauds { get; } = new()
+ {
+ "1000000", "2000000", "4000000", "5000000", "8000000"
+ };
+
+ private string _errorMessage = string.Empty;
+ public string ErrorMessage
+ {
+ get => _errorMessage;
+ set => SetProperty(ref _errorMessage, value);
+ }
+
+ #endregion
+
+ #region 命令
+ public ICommand SaveCommand { get; }
+ public ICommand CancelCommand { get; }
+ #endregion
+
+ // 用于保存时把副本回写到原对象
+ private DeviceInfoVM? _hostDevice;
+
+ public CANConfigViewModel(IContainerProvider containerProvider) : base(containerProvider)
+ {
+ SaveCommand = new DelegateCommand(OnSave);
+ CancelCommand = new DelegateCommand(OnCancel);
+ }
+
+ private bool Validate(out string error)
+ {
+ error = string.Empty;
+ if (Config.DeviceType == 0)
+ {
+ error = "设备类型号不能为 0";
+ return false;
+ }
+ if (string.IsNullOrWhiteSpace(Config.ABitBaud))
+ {
+ error = "仲裁域波特率不能为空";
+ return false;
+ }
+ if (string.IsNullOrWhiteSpace(Config.DBitBaud))
+ {
+ error = "数据域波特率不能为空";
+ return false;
+ }
+ return true;
+ }
+
+ private void OnSave()
+ {
+ if (!Validate(out var error))
+ {
+ ErrorMessage = error;
+ return;
+ }
+ ErrorMessage = string.Empty;
+
+ // 把副本写回宿主
+ if (_hostDevice != null)
+ {
+ _hostDevice.CANConfig ??= new CANConfigVM();
+ Config.CopyTo(_hostDevice.CANConfig);
+ _hostDevice.ConnectionType = "CAN";
+ }
+
+ RequestClose.Invoke(ButtonResult.OK);
+ }
+
+ private void OnCancel() => RequestClose.Invoke(ButtonResult.Cancel);
+
+ #region Prism Dialog 规范
+ public override void OnDialogOpened(IDialogParameters parameters)
+ {
+ _eventAggregator.GetEvent().Publish(true);
+
+ if (parameters.ContainsKey("Device"))
+ {
+ _hostDevice = parameters.GetValue("Device");
+ Title = $"CAN 连接配置 - {_hostDevice?.DeviceName}";
+ Config = new CANConfigVM(_hostDevice?.CANConfig);
+ }
+ else if (parameters.ContainsKey("Config"))
+ {
+ Config = new CANConfigVM(parameters.GetValue("Config"));
+ }
+ }
+
+ public override void OnDialogClosed()
+ {
+ _eventAggregator.GetEvent().Publish(false);
+ }
+ #endregion
+ }
+}
diff --git a/SettingModule/ViewModels/SettingViewModel.cs b/SettingModule/ViewModels/SettingViewModel.cs
index c9156d2..78ce317 100644
--- a/SettingModule/ViewModels/SettingViewModel.cs
+++ b/SettingModule/ViewModels/SettingViewModel.cs
@@ -59,10 +59,9 @@ namespace SettingModule.ViewModels
public ObservableCollection ConnectionTypes { get; } = new()
{
- "None", "Tcp", "Serial"
+ "None", "Tcp", "Serial", "CAN"
};
#endregion
-
#region 命令
public ICommand RefreshCommand { get; }
public ICommand SaveCommand { get; }
@@ -165,6 +164,7 @@ namespace SettingModule.ViewModels
{
"Tcp" => "TCPConfig",
"Serial" => "SerialPortConfig",
+ "CAN" => "CANConfig",
_ => string.Empty
};
@@ -200,6 +200,7 @@ namespace SettingModule.ViewModels
_scope = _globalInfo.ScopeDic[TestStatus];
_scopedContext = _globalInfo.ContextDic[TestStatus];
SystemConfig = _scope.Resolve();
+ ConfigService.EnsureDefaultCanDevice(SystemConfig);
if (DeviceList != null && DeviceList.Count > 0)
{
SelectedDevice = DeviceList[0];
diff --git a/SettingModule/Views/Dialogs/CANConfigView.xaml b/SettingModule/Views/Dialogs/CANConfigView.xaml
new file mode 100644
index 0000000..5c6766f
--- /dev/null
+++ b/SettingModule/Views/Dialogs/CANConfigView.xaml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SettingModule/Views/Dialogs/CANConfigView.xaml.cs b/SettingModule/Views/Dialogs/CANConfigView.xaml.cs
new file mode 100644
index 0000000..292f936
--- /dev/null
+++ b/SettingModule/Views/Dialogs/CANConfigView.xaml.cs
@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+namespace SettingModule.Views.Dialogs
+{
+ ///
+ /// CANConfigView.xaml 的交互逻辑
+ ///
+ public partial class CANConfigView : UserControl
+ {
+ public CANConfigView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/SettingModule/Views/SettingView.xaml b/SettingModule/Views/SettingView.xaml
index aa99955..6df1253 100644
--- a/SettingModule/Views/SettingView.xaml
+++ b/SettingModule/Views/SettingView.xaml
@@ -368,6 +368,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UIShare/GlobalVariable/ConfigService.cs b/UIShare/GlobalVariable/ConfigService.cs
index e088918..f8d5de2 100644
--- a/UIShare/GlobalVariable/ConfigService.cs
+++ b/UIShare/GlobalVariable/ConfigService.cs
@@ -1,7 +1,10 @@
using System;
+using System.Collections.ObjectModel;
using System.IO;
+using System.Linq;
using Newtonsoft.Json;
using Logger;
+using UIShare.UIViewModel;
namespace UIShare.GlobalVariable
{
@@ -44,6 +47,7 @@ namespace UIShare.GlobalVariable
{
// 如果不存在,创建一个带 Title 的默认配置并保存
var defaultConfig = new SystemConfig { Title = title };
+ EnsureDefaultCanDevice(defaultConfig);
Save(defaultConfig);
return defaultConfig;
}
@@ -58,12 +62,16 @@ namespace UIShare.GlobalVariable
TypeNameHandling = TypeNameHandling.All
});
- return config ?? new SystemConfig { Title = title };
+ config ??= new SystemConfig { Title = title };
+ EnsureDefaultCanDevice(config);
+ return config;
}
catch (Exception ex)
{
LoggerHelper.ErrorWithNotify(title, $"格子 [{title}] 配置加载失败: {ex.Message}");
- return new SystemConfig { Title = title };
+ var fallback = new SystemConfig { Title = title };
+ EnsureDefaultCanDevice(fallback);
+ return fallback;
}
}
}
@@ -98,5 +106,34 @@ namespace UIShare.GlobalVariable
}
}
}
+
+ ///
+ /// 确保配置中至少包含一个 CAN 设备(对应 SystemConfig.CANFD)。
+ /// 旧配置或空配置会自动升级,使用户在设置界面能看到 CAN 设备。
+ ///
+ public static void EnsureDefaultCanDevice(SystemConfig config)
+ {
+ if (config.DeviceList == null)
+ {
+ config.DeviceList = new ObservableCollection();
+ }
+
+ bool hasCan = config.DeviceList.Any(d =>
+ string.Equals(d?.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(d?.DeviceType, "ZLGCANFD", StringComparison.OrdinalIgnoreCase));
+
+ if (!hasCan)
+ {
+ config.DeviceList.Add(new DeviceInfoVM
+ {
+ DeviceName = "CAN",
+ DeviceType = "ZLGCANFD",
+ Remark = "周立功 CANFD 接口卡",
+ ConnectionType = "CAN",
+ IsEnabled = true,
+ IsConnected = false
+ });
+ }
+ }
}
}
\ No newline at end of file
diff --git a/UIShare/GlobalVariable/DeviceManager.cs b/UIShare/GlobalVariable/DeviceManager.cs
index 6c0d1a8..1eeab02 100644
--- a/UIShare/GlobalVariable/DeviceManager.cs
+++ b/UIShare/GlobalVariable/DeviceManager.cs
@@ -8,6 +8,7 @@ using System.IO.Ports;
using System.Linq;
using System.Reflection;
using UIShare.UIViewModel;
+using ZLGUSBCANFD;
namespace UIShare.GlobalVariable
{
@@ -28,6 +29,7 @@ namespace UIShare.GlobalVariable
/// 类名 → Type 的反射缓存(仅扫描一次)。
private static readonly IReadOnlyDictionary _deviceTypeMap = BuildDeviceTypeMap();
+ public ZLGCANFD CANFD { get; set; }
public DeviceManager(SystemConfig systemConfig, GlobalInfo globalInfo)
{
@@ -37,6 +39,7 @@ namespace UIShare.GlobalVariable
_scopeName = _systemConfig.Title;
InitDevices();
}
+
///
/// 根据设备配置提取唯一的硬件指纹字符串。
/// Tcp → "Tcp:IP:Port";Serial → "Serial:PortName";无法识别则返回空字符串。
@@ -55,9 +58,14 @@ namespace UIShare.GlobalVariable
return $"Serial:{config.SerialPortConfig.PortName}";
}
+ if (string.Equals(config.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase)
+ && config.CANConfig != null)
+ {
+ return $"CAN:{config.CANConfig.DeviceIndex}";
+ }
+
return string.Empty;
}
-
private void InitDevices()
{
DeviceMap = new Dictionary(StringComparer.OrdinalIgnoreCase);
@@ -68,6 +76,53 @@ namespace UIShare.GlobalVariable
{
if (config == null || !config.IsEnabled) continue;
+ // CAN 设备:ZLGCANFD 不实现 IBaseInterface,通过 SystemConfig.CANFD 单独管理,
+ // 按指纹从全局 CanPool 创建/复用实例,并注册作用域引用计数。
+ if (string.Equals(config.ConnectionType, "CAN", StringComparison.OrdinalIgnoreCase))
+ {
+ var fingerprint = ExtractHardwareFingerprint(config);
+ if (string.IsNullOrEmpty(fingerprint))
+ {
+ LoggerHelper.Warn($"设备 [{config.DeviceName}] 无法提取硬件指纹(连接方式={config.ConnectionType}),已跳过。");
+ continue;
+ }
+
+ if (config.CANConfig == null)
+ {
+ LoggerHelper.Warn($"设备 [{config.DeviceName}] 缺少 CAN 连接参数,已跳过。");
+ continue;
+ }
+
+ try
+ {
+ // 按指纹全局唯一创建 ZLGCANFD 实例(maxChannels 默认 4,对应 USBCANFD-400U)
+ var canLazy = _globalInfo.CanPool.GetOrAdd(fingerprint, key => new Lazy(() =>
+ new ZLGCANFD(config.CANConfig.DeviceType, config.CANConfig.DeviceIndex, 4)));
+
+ _systemConfig.CANFD = canLazy.Value;
+ CANFD = canLazy.Value;
+
+ // 注册作用域引用计数
+ if (!string.IsNullOrEmpty(_scopeName))
+ {
+ var scopeList = _globalInfo.DeviceAndScopeDic.GetOrAdd(fingerprint,
+ _ => new Lazy>(() => new List())).Value;
+ lock (scopeList)
+ {
+ if (!scopeList.Contains(_scopeName))
+ scopeList.Add(_scopeName);
+ }
+ }
+
+ LoggerHelper.Info($"已加载 CAN 设备 [{config.DeviceName}] 指纹={fingerprint}(通过 SystemConfig.CANFD 管理)");
+ }
+ catch (Exception ex)
+ {
+ LoggerHelper.ErrorWithNotify(_scopeName, $"CAN 设备 [{config.DeviceName}] 实例化失败:{ex.Message}");
+ }
+ continue;
+ }
+
if (string.IsNullOrWhiteSpace(config.DeviceType) ||
!_deviceTypeMap.TryGetValue(config.DeviceType, out var deviceType))
{
@@ -391,6 +446,17 @@ namespace UIShare.GlobalVariable
}
}
+ // 尝试从 CanPool 取出并销毁
+ if (_globalInfo.CanPool.TryRemove(fingerprint, out var canLazy))
+ {
+ if (canLazy.IsValueCreated)
+ {
+ try { canLazy.Value.Dispose(); }
+ catch { /* 销毁时忽略异常 */ }
+ LoggerHelper.Info($"指纹 [{fingerprint}] 无作用域引用,已销毁 CAN 设备实例。");
+ }
+ }
+
// 同步清除 DeviceAndScopeDic 中的空条目
_globalInfo.DeviceAndScopeDic.TryRemove(fingerprint, out _);
}
diff --git a/UIShare/GlobalVariable/GlobalInfo.cs b/UIShare/GlobalVariable/GlobalInfo.cs
index 2e7f0f1..305e81e 100644
--- a/UIShare/GlobalVariable/GlobalInfo.cs
+++ b/UIShare/GlobalVariable/GlobalInfo.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using ZLGUSBCANFD;
namespace UIShare.GlobalVariable
{
@@ -19,6 +20,9 @@ namespace UIShare.GlobalVariable
/// 硬件指纹 → 设备实例的并发池,确保同一物理硬件全局只创建一个驱动实例。
public ConcurrentDictionary> HardwarePool { get; set; }
+ /// CAN 硬件指纹 → ZLGCANFD 实例的并发池,确保同一 CAN 卡全局只创建一个驱动实例。
+ public ConcurrentDictionary> CanPool { get; set; }
+
/// 硬件指纹 → 正在使用该设备的作用域名称列表,用于引用计数与安全销毁。
public ConcurrentDictionary>> DeviceAndScopeDic { get; set; }
@@ -45,6 +49,7 @@ namespace UIShare.GlobalVariable
ConfigDic = new();
ScopeDic = new();
HardwarePool = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase);
+ CanPool = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase);
DeviceAndScopeDic = new ConcurrentDictionary>>(StringComparer.OrdinalIgnoreCase);
CurrentScope = "default";
}
diff --git a/UIShare/GlobalVariable/StepRunning.cs b/UIShare/GlobalVariable/StepRunning.cs
index 54f3c3d..a93959e 100644
--- a/UIShare/GlobalVariable/StepRunning.cs
+++ b/UIShare/GlobalVariable/StepRunning.cs
@@ -564,7 +564,11 @@ namespace UIShare
{
try
{
- instance = _deviceManager.DeviceMap[targetType.Name];
+ if(targetType.Name== "ZLGCANFD")
+ {
+ instance = _deviceManager.CANFD;
+ }
+ else instance = _deviceManager.DeviceMap[targetType.Name];
}
catch (Exception ex)
{
diff --git a/UIShare/GlobalVariable/SystemConfig.cs b/UIShare/GlobalVariable/SystemConfig.cs
index f43b872..a120d3f 100644
--- a/UIShare/GlobalVariable/SystemConfig.cs
+++ b/UIShare/GlobalVariable/SystemConfig.cs
@@ -9,6 +9,7 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UIShare.UIViewModel;
+using ZLGUSBCANFD;
using static UIShare.UIViewModel.ParameterVM;
namespace UIShare.GlobalVariable
@@ -29,6 +30,7 @@ namespace UIShare.GlobalVariable
public string DefaultDBCFilePath { get; set; } = "";
public ObservableCollection DeviceList = new();
public ObservableCollection SharedParameterList = new();
+ public ZLGCANFD CANFD = new();
[JsonIgnore]
public ObservableCollection ParameterList = new()
{
diff --git a/UIShare/UIShare.csproj b/UIShare/UIShare.csproj
index 24ddae0..e8edad5 100644
--- a/UIShare/UIShare.csproj
+++ b/UIShare/UIShare.csproj
@@ -20,5 +20,6 @@
+
diff --git a/UIShare/UIViewModel/CANConfigVM.cs b/UIShare/UIViewModel/CANConfigVM.cs
new file mode 100644
index 0000000..e860452
--- /dev/null
+++ b/UIShare/UIViewModel/CANConfigVM.cs
@@ -0,0 +1,76 @@
+using Prism.Mvvm;
+
+namespace UIShare.UIViewModel
+{
+ ///
+ /// CAN 连接配置(对应 ZLGCANFD 构造函数 + 初始化并启动通道 参数)。
+ ///
+ public class CANConfigVM : BindableBase
+ {
+ // ===== ZLGCANFD 构造函数参数 =====
+
+ private uint _deviceType = 43;
+ /// 设备类型号(43 = USBCANFD-400U)
+ public uint DeviceType
+ {
+ get => _deviceType;
+ set => SetProperty(ref _deviceType, value);
+ }
+
+ private uint _deviceIndex = 0;
+ /// 设备索引
+ public uint DeviceIndex
+ {
+ get => _deviceIndex;
+ set => SetProperty(ref _deviceIndex, value);
+ }
+
+ private string _abitBaud = "500000";
+ /// 仲裁域波特率
+ public string ABitBaud
+ {
+ get => _abitBaud;
+ set => SetProperty(ref _abitBaud, value);
+ }
+
+ private string _dbitBaud = "2000000";
+ /// 数据域波特率
+ public string DBitBaud
+ {
+ get => _dbitBaud;
+ set => SetProperty(ref _dbitBaud, value);
+ }
+
+ private bool _enableTerminalResistance = true;
+ /// 是否开启终端电阻
+ public bool EnableTerminalResistance
+ {
+ get => _enableTerminalResistance;
+ set => SetProperty(ref _enableTerminalResistance, value);
+ }
+
+ public CANConfigVM() { }
+
+ /// 拷贝构造,用于对话框编辑副本。
+ public CANConfigVM(CANConfigVM? src)
+ {
+ if (src == null) return;
+ DeviceType = src.DeviceType;
+ DeviceIndex = src.DeviceIndex;
+ ABitBaud = src.ABitBaud;
+ DBitBaud = src.DBitBaud;
+ EnableTerminalResistance = src.EnableTerminalResistance;
+ }
+
+ /// 把字段拷回目标对象(保存时用)。
+ public void CopyTo(CANConfigVM? dst)
+ {
+ if (dst == null) return;
+ dst.DeviceType = DeviceType;
+ dst.DeviceIndex = DeviceIndex;
+ dst.ABitBaud = ABitBaud;
+ dst.DBitBaud = DBitBaud;
+ dst.EnableTerminalResistance = EnableTerminalResistance;
+ }
+ }
+}
diff --git a/UIShare/UIViewModel/DeviceInfoVM.cs b/UIShare/UIViewModel/DeviceInfoVM.cs
index d3c03e8..bab204c 100644
--- a/UIShare/UIViewModel/DeviceInfoVM.cs
+++ b/UIShare/UIViewModel/DeviceInfoVM.cs
@@ -65,5 +65,13 @@ namespace UIShare.UIViewModel
get => _serialPortConfig;
set => SetProperty(ref _serialPortConfig, value);
}
+
+ /// CAN 连接参数(对应 ZLGCANFD 构造 + 通道初始化参数)。
+ private CANConfigVM _canConfig = new();
+ public CANConfigVM CANConfig
+ {
+ get => _canConfig;
+ set => SetProperty(ref _canConfig, value);
+ }
}
}
diff --git a/USBCANFD/USBCANFD.cs b/USBCANFD/USBCANFD.cs
deleted file mode 100644
index 9a88180..0000000
--- a/USBCANFD/USBCANFD.cs
+++ /dev/null
@@ -1,400 +0,0 @@
-using Common.Attributes;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using USBCANFD;
-
-namespace DeviceCommand.Device
-{
- [ADPCommand]
- public class ZLGCANFD : IDisposable
- {
- // 硬件设备与通道句柄
- private IntPtr _deviceHandle = IntPtr.Zero;
- private readonly IntPtr[] _channelHandles;
-
- // 【核心修改】每个通道分配独立的 DBC 引擎句柄与锁,彻底解决台架间DBC冲突与锁竞争
- private readonly uint[] _dbcHandles;
- private readonly bool[] _isDbcLoadedArray;
- private readonly object[] _channelLocks; // 通道级细粒度锁
-
- // 异步高性能接收线程控制
- private volatile bool _isRunning = false;
- private readonly List _receiveThreads = new List();
-
- // 动态硬件参数
- private readonly uint _deviceType; // 43: USBCANFD-400U
- private readonly uint _deviceIndex; // 设备索引
- private readonly int _maxChannels; // 动态通道数
-
- ///
- /// 事件:当接收到 CAN/CANFD 报文并且通过 DBC 成功解析后触发
- /// 参数:uint 通道号 (0,1,2,3), ZDBC.DBCMessage 解析后的DBC消息结构体
- ///
- public event Action? OnDbcMessageDecoded;
-
- public ZLGCANFD(uint deviceType = 43, uint deviceIndex = 0, int maxChannels = 4)
- {
- _deviceType = deviceType;
- _deviceIndex = deviceIndex;
- _maxChannels = maxChannels;
-
- // 初始化通道相关状态数组
- _channelHandles = new IntPtr[_maxChannels];
- _dbcHandles = new uint[_maxChannels];
- _isDbcLoadedArray = new bool[_maxChannels];
- _channelLocks = new object[_maxChannels];
-
- for (int i = 0; i < _maxChannels; i++)
- {
- _channelHandles[i] = IntPtr.Zero;
- _dbcHandles[i] = 0;
- _isDbcLoadedArray[i] = false;
- _channelLocks[i] = new object();
- }
- }
-
- #region 1. 硬件连接与通道初始化
-
- ///
- /// 仅仅打开设备,不做具体的通道波特率配置(留给具体的台架去分别配置)
- ///
- public virtual bool 打开设备()
- {
- if (_deviceHandle != IntPtr.Zero) return true;
- _deviceHandle = ZLGCAN.ZCAN_OpenDevice(_deviceType, _deviceIndex, 0);
- return _deviceHandle != IntPtr.Zero;
- }
-
- ///
- /// 【核心修改】针对特定通道进行参数初始化并启动
- ///
- public virtual bool 初始化并启动通道(uint 通道号, string abitBaud = "500000", string dbitBaud = "2000000", bool 开启终端电阻 = true)
- {
- if (_deviceHandle == IntPtr.Zero) throw new InvalidOperationException("请先调用 '打开设备()' 才能初始化通道。");
- if (通道号 >= _maxChannels) return false;
-
- lock (_channelLocks[通道号])
- {
- // 如果已经启动过,先复位
- if (_channelHandles[通道号] != IntPtr.Zero)
- {
- ZLGCAN.ZCAN_ResetCAN(_channelHandles[通道号]);
- }
-
- // 1. 设置该通道专属的仲裁域与数据域波特率
- if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/canfd_abit_baud_rate", abitBaud) != 1) return false;
- if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/canfd_dbit_baud_rate", dbitBaud) != 1) return false;
-
- // 2. 设置该通道专属内部终端电阻状态
- string resistanceStr = 开启终端电阻 ? "1" : "0";
- if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/initenal_resistance", resistanceStr) != 1) return false;
-
- // 3. 规范配置通道结构体
- ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG config = new ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG();
- config.can_type = 1; // 1 代表 CANFD 模式
- config.config.canfd.mode = 0; // 0 代表正常工作模式
-
- IntPtr pConfig = Marshal.AllocHGlobal(Marshal.SizeOf(config));
- Marshal.StructureToPtr(config, pConfig, true);
- _channelHandles[通道号] = ZLGCAN.ZCAN_InitCAN(_deviceHandle, 通道号, pConfig);
- Marshal.FreeHGlobal(pConfig);
-
- if (_channelHandles[通道号] == IntPtr.Zero) return false;
-
- // 4. 启动 CAN 通道
- if (ZLGCAN.ZCAN_StartCAN(_channelHandles[通道号]) != 1) return false;
-
- // 5. 为该通道启动专属的独立后台高性能轮询接收线程(如果尚未启动轮询)
- if (!_isRunning) _isRunning = true;
-
- int chnIdx = (int)通道号;
- Thread rxThread = new Thread(() => 接收轮询核心(_channelHandles[chnIdx], (uint)chnIdx))
- {
- IsBackground = true,
- Name = $"ZLGCANFD_Dev{_deviceIndex}_CH{chnIdx}_RxThread"
- };
- _receiveThreads.Add(rxThread);
- rxThread.Start();
-
- return true;
- }
- }
-
- public virtual void 关闭CAN卡设备()
- {
- _isRunning = false;
- Thread.Sleep(50); // 确保轮询线程安全退出
- _receiveThreads.Clear();
-
- // 动态复位所有通道并释放DBC
- for (uint i = 0; i < _maxChannels; i++)
- {
- lock (_channelLocks[i])
- {
- if (_channelHandles[i] != IntPtr.Zero)
- {
- ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]);
- _channelHandles[i] = IntPtr.Zero;
- }
- 释放通道DBC(i);
- }
- }
-
- // 关闭设备主句柄
- if (_deviceHandle != IntPtr.Zero)
- {
- ZLGCAN.ZCAN_CloseDevice(_deviceHandle);
- _deviceHandle = IntPtr.Zero;
- }
- }
-
- #endregion
-
- #region 2. DBC 矩阵文件操作
-
- ///
- /// 【核心修改】指定通道加载特定的 DBC 文件(支持不同通道加载不同的DBC矩阵)
- ///
- public virtual bool 加载通道DBC文件(uint 通道号, string dbcFilePath)
- {
- if (通道号 >= _maxChannels) return false;
-
- lock (_channelLocks[通道号])
- {
- try
- {
- if (_isDbcLoadedArray[通道号]) return true;
-
- uint chDbcHandle = ZDBC.ZDBC_Init();
- if (chDbcHandle == 0) return false;
-
- IntPtr ptrPath = Marshal.StringToHGlobalAnsi(dbcFilePath);
- bool success = ZDBC.ZDBC_LoadFile(chDbcHandle, ptrPath);
- Marshal.FreeHGlobal(ptrPath);
-
- if (success)
- {
- _dbcHandles[通道号] = chDbcHandle;
- _isDbcLoadedArray[通道号] = true;
- }
- else
- {
- ZDBC.ZDBC_Release(chDbcHandle);
- }
- return success;
- }
- catch
- {
- return false;
- }
- }
- }
-
- public virtual void 释放通道DBC(uint 通道号)
- {
- if (通道号 >= _maxChannels) return;
-
- lock (_channelLocks[通道号])
- {
- if (_isDbcLoadedArray[通道号] && _dbcHandles[通道号] != 0)
- {
- ZDBC.ZDBC_Release(_dbcHandles[通道号]);
- _dbcHandles[通道号] = 0;
- _isDbcLoadedArray[通道号] = false;
- }
- }
- }
-
- #endregion
-
- #region 3. 轮询接收与实时自动化 DBC 解码
-
- private void 接收轮询核心(IntPtr channelHandle, uint channelIndex)
- {
- const int bufferSize = 100;
- int canStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Receive_Data));
- int canfdStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_ReceiveFD_Data));
- int dbcMsgSize = Marshal.SizeOf(typeof(ZDBC.DBCMessage));
-
- IntPtr ptrCanBuffer = Marshal.AllocHGlobal(canStructSize * bufferSize);
- IntPtr ptrCanFDBuffer = Marshal.AllocHGlobal(canfdStructSize * bufferSize);
- IntPtr ptrDbcMsg = Marshal.AllocHGlobal(dbcMsgSize);
-
- try
- {
- while (_isRunning)
- {
- bool currentTurnHasData = false;
-
- // 1. 自动提取并解析经典 CAN 帧
- uint canNum = ZLGCAN.ZCAN_GetReceiveNum(channelHandle, 0);
- if (canNum > 0)
- {
- uint actualRecv = ZLGCAN.ZCAN_Receive(channelHandle, ptrCanBuffer, bufferSize, 5);
- for (int i = 0; i < actualRecv; i++)
- {
- IntPtr framePtr = IntPtr.Add(ptrCanBuffer, i * canStructSize);
-
- // 【通道级业务隔离隔离】只锁定当前通道的DBC句柄进行解码,高并发完全不卡顿
- lock (_channelLocks[channelIndex])
- {
- if (_isDbcLoadedArray[channelIndex] && ZDBC.ZDBC_Decode(_dbcHandles[channelIndex], ptrDbcMsg, framePtr, 1, 0))
- {
- var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
- OnDbcMessageDecoded?.Invoke(channelIndex, msg);
- }
- }
- }
- currentTurnHasData = true;
- }
-
- // 2. 自动提取并解析高速 CANFD 帧
- uint canfdNum = ZLGCAN.ZCAN_GetReceiveNum(channelHandle, 1);
- if (canfdNum > 0)
- {
- uint actualRecvFd = ZLGCAN.ZCAN_ReceiveFD(channelHandle, ptrCanFDBuffer, bufferSize, 5);
- for (int i = 0; i < actualRecvFd; i++)
- {
- IntPtr framePtr = IntPtr.Add(ptrCanFDBuffer, i * canfdStructSize);
-
- lock (_channelLocks[channelIndex])
- {
- if (_isDbcLoadedArray[channelIndex] && ZDBC.ZDBC_Decode(_dbcHandles[channelIndex], ptrDbcMsg, framePtr, 1, 1))
- {
- var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
- OnDbcMessageDecoded?.Invoke(channelIndex, msg);
- }
- }
- }
- currentTurnHasData = true;
- }
-
- if (!currentTurnHasData)
- {
- Thread.Sleep(2);
- }
- }
- }
- finally
- {
- Marshal.FreeHGlobal(ptrCanBuffer);
- Marshal.FreeHGlobal(ptrCanFDBuffer);
- Marshal.FreeHGlobal(ptrDbcMsg);
- }
- }
-
- #endregion
-
- #region 4. DBC 自动打包与智能报文发送
-
- public virtual bool 发送DBC定义报文(uint 通道号, uint 帧ID, Action 配置信号动作, int 是否使用CANFD = 1)
- {
- if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
- if (!_isDbcLoadedArray[通道号]) throw new InvalidOperationException($"通道 {通道号} 的 DBC 协议未加载,无法执行打包发送指令。");
-
- IntPtr ptrDbcMsg = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCMessage)));
- IntPtr ptrCount = Marshal.AllocHGlobal(sizeof(uint));
- Marshal.WriteInt32(ptrCount, 1);
-
- try
- {
- // 使用通道级锁保护当前通道发送
- lock (_channelLocks[通道号])
- {
- uint chDbcHandle = _dbcHandles[通道号];
-
- if (!ZDBC.ZDBC_GetMessageById(chDbcHandle, 帧ID, ptrDbcMsg)) return false;
- var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
-
- 配置信号动作(msg);
- Marshal.StructureToPtr(msg, ptrDbcMsg, true);
-
- if (是否使用CANFD == 0)
- {
- IntPtr ptrCanFrame = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.can_frame)));
- if (!ZDBC.ZDBC_Encode(chDbcHandle, ptrCanFrame, ptrCount, ptrDbcMsg, 0)) return false;
-
- ZLGCAN.can_frame canFrame = (ZLGCAN.can_frame)Marshal.PtrToStructure(ptrCanFrame, typeof(ZLGCAN.can_frame));
- canFrame.__pad |= 0x20;
-
- ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = canFrame, transmit_type = 0 };
- IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData));
- Marshal.StructureToPtr(txData, pTx, true);
-
- uint sendResult = ZLGCAN.ZCAN_Transmit(_channelHandles[通道号], pTx, 1);
-
- Marshal.FreeHGlobal(ptrCanFrame);
- Marshal.FreeHGlobal(pTx);
- return sendResult == 1;
- }
- else
- {
- IntPtr ptrCanFDFrame = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.canfd_frame)));
- if (!ZDBC.ZDBC_Encode(chDbcHandle, ptrCanFDFrame, ptrCount, ptrDbcMsg, 1)) return false;
-
- ZLGCAN.canfd_frame canfdFrame = (ZLGCAN.canfd_frame)Marshal.PtrToStructure(ptrCanFDFrame, typeof(ZLGCAN.canfd_frame));
- canfdFrame.flags |= 0x20;
-
- ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = canfdFrame, transmit_type = 0 };
- IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData));
- Marshal.StructureToPtr(txFdData, pTxFd, true);
-
- uint sendResult = ZLGCAN.ZCAN_TransmitFD(_channelHandles[通道号], pTxFd, 1);
-
- Marshal.FreeHGlobal(ptrCanFDFrame);
- Marshal.FreeHGlobal(pTxFd);
- return sendResult == 1;
- }
- }
- }
- finally
- {
- Marshal.FreeHGlobal(ptrDbcMsg);
- Marshal.FreeHGlobal(ptrCount);
- }
- }
-
- #endregion
-
- #region 5. 辅助工具子系统
-
- ///
- /// 【核心修改】计算物理值也需要传入对应通道,使用通道专属的DBC句柄进行计算
- ///
- public ulong 物理值转原始寄存器值(uint 通道号, ZDBC.DBCSignal 信号定义, double 实际物理值)
- {
- if (通道号 >= _maxChannels) return 0;
-
- IntPtr pSignal = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCSignal)));
- IntPtr pValue = Marshal.AllocHGlobal(sizeof(double));
-
- try
- {
- Marshal.StructureToPtr(信号定义, pSignal, true);
- Marshal.StructureToPtr(实际物理值, pValue, true);
- lock (_channelLocks[通道号])
- {
- return ZDBC.ZDBC_CalcRawValue(pSignal, pValue);
- }
- }
- finally
- {
- Marshal.FreeHGlobal(pSignal);
- Marshal.FreeHGlobal(pValue);
- }
- }
-
- #endregion
-
- [Browsable(false)]
- public void Dispose()
- {
- 关闭CAN卡设备();
- }
- }
-}
\ No newline at end of file
diff --git a/ZLGUSBCANFD/USBCANFD.cs b/ZLGUSBCANFD/USBCANFD.cs
new file mode 100644
index 0000000..34698bf
--- /dev/null
+++ b/ZLGUSBCANFD/USBCANFD.cs
@@ -0,0 +1,729 @@
+using Common.Attributes;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ZLGUSBCANFD
+{
+ [ADPCommand]
+ public class ZLGCANFD : IDisposable
+ {
+ // 硬件设备与通道句柄
+ private IntPtr _deviceHandle = IntPtr.Zero;
+ private readonly IntPtr[] _channelHandles;
+
+ // 每个通道分配独立的 DBC 引擎句柄与锁,彻底解决台架间DBC冲突与锁竞争
+ private readonly uint[] _dbcHandles;
+ private readonly bool[] _isDbcLoadedArray;
+ private readonly object[] _channelLocks; // 通道级细粒度锁
+
+ // 异步高性能接收线程控制
+ private volatile bool _isRunning = false;
+ private readonly List _receiveThreads = new List();
+
+ // DBC 循环发送任务管理:Key = (通道号, 帧ID)
+ private readonly ConcurrentDictionary<(uint 通道号, uint 帧ID), CancellationTokenSource> _cyclicSenders = new ConcurrentDictionary<(uint, uint), CancellationTokenSource>();
+
+ // 动态硬件参数
+ private readonly uint _deviceType; // 76: USBCANFD-400U
+ private readonly uint _deviceIndex; // 设备索引
+ private readonly int _maxChannels; // 动态通道数
+
+ ///
+ /// 事件:当接收到 CAN/CANFD 报文并且通过 DBC 成功解析后触发
+ /// 参数:uint 通道号 (0,1,2,3), ZDBC.DBCMessage 解析后的DBC消息结构体
+ ///
+ public event Action? OnDbcMessageDecoded;
+
+ public ZLGCANFD(uint deviceType = 76, uint deviceIndex = 0, int maxChannels = 4)
+ {
+ _deviceType = deviceType;
+ _deviceIndex = deviceIndex;
+ _maxChannels = maxChannels;
+
+ // 初始化通道相关状态数组
+ _channelHandles = new IntPtr[_maxChannels];
+ _dbcHandles = new uint[_maxChannels];
+ _isDbcLoadedArray = new bool[_maxChannels];
+ _channelLocks = new object[_maxChannels];
+
+ for (int i = 0; i < _maxChannels; i++)
+ {
+ _channelHandles[i] = IntPtr.Zero;
+ _dbcHandles[i] = 0;
+ _isDbcLoadedArray[i] = false;
+ _channelLocks[i] = new object();
+ }
+ }
+
+ #region 1. 硬件连接与通道初始化
+
+ ///
+ /// 仅仅打开设备,不做具体的通道波特率配置(留给具体的台架去分别配置)
+ ///
+ public virtual bool 打开设备()
+ {
+ if (_deviceHandle != IntPtr.Zero) return true;
+ _deviceHandle = ZLGCAN.ZCAN_OpenDevice(ZLGCAN.ZCAN_USBCANFD_400U, _deviceIndex, 0);
+ return _deviceHandle != IntPtr.Zero;
+ }
+
+ ///
+ /// 针对特定通道进行参数初始化并启动
+ ///
+ public virtual bool 初始化并启动通道(uint 通道号, string abitBaud = "500000", string dbitBaud = "2000000", bool 开启终端电阻 = true)
+ {
+ if (_deviceHandle == IntPtr.Zero) throw new InvalidOperationException("请先调用 '打开设备()' 才能初始化通道。");
+ if (通道号 >= _maxChannels) return false;
+
+ lock (_channelLocks[通道号])
+ {
+ // 如果已经启动过,先复位
+ if (_channelHandles[通道号] != IntPtr.Zero)
+ {
+ ZLGCAN.ZCAN_ResetCAN(_channelHandles[通道号]);
+ }
+
+ // 1. 设置该通道专属的仲裁域与数据域波特率
+ if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/canfd_abit_baud_rate", abitBaud) != 1) return false;
+ if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/canfd_dbit_baud_rate", dbitBaud) != 1) return false;
+
+ // 2. 设置该通道专属内部终端电阻状态
+ string resistanceStr = 开启终端电阻 ? "1" : "0";
+ if (ZLGCAN.ZCAN_SetValue(_deviceHandle, $"{通道号}/initenal_resistance", resistanceStr) != 1) return false;
+
+ // 3. 规范配置通道结构体
+ ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG config = new ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG();
+ config.can_type = 1; // 1 代表 CANFD 模式
+ config.config.canfd.mode = 0; // 0 代表正常工作模式
+
+ IntPtr pConfig = Marshal.AllocHGlobal(Marshal.SizeOf(config));
+ Marshal.StructureToPtr(config, pConfig, true);
+ _channelHandles[通道号] = ZLGCAN.ZCAN_InitCAN(_deviceHandle, 通道号, pConfig);
+ Marshal.FreeHGlobal(pConfig);
+
+ if (_channelHandles[通道号] == IntPtr.Zero) return false;
+
+ // 4. 启动 CAN 通道
+ if (ZLGCAN.ZCAN_StartCAN(_channelHandles[通道号]) != 1) return false;
+
+ // 5. 为该通道启动专属的独立后台高性能轮询接收线程(如果尚未启动轮询)
+ if (!_isRunning) _isRunning = true;
+
+ int chnIdx = (int)通道号;
+ Thread rxThread = new Thread(() => 接收轮询核心(_channelHandles[chnIdx], (uint)chnIdx))
+ {
+ IsBackground = true,
+ Name = $"ZLGCANFD_Dev{_deviceIndex}_CH{chnIdx}_RxThread"
+ };
+ _receiveThreads.Add(rxThread);
+ rxThread.Start();
+
+ return true;
+ }
+ }
+
+ public virtual void 关闭CAN卡设备()
+ {
+ _isRunning = false;
+ 停止所有循环发送();
+ Thread.Sleep(50); // 确保轮询线程安全退出
+ _receiveThreads.Clear();
+
+ // 动态复位所有通道并释放DBC
+ for (uint i = 0; i < _maxChannels; i++)
+ {
+ lock (_channelLocks[i])
+ {
+ if (_channelHandles[i] != IntPtr.Zero)
+ {
+ ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]);
+ _channelHandles[i] = IntPtr.Zero;
+ }
+ 释放通道DBC(i);
+ }
+ }
+
+ // 关闭设备主句柄
+ if (_deviceHandle != IntPtr.Zero)
+ {
+ ZLGCAN.ZCAN_CloseDevice(_deviceHandle);
+ _deviceHandle = IntPtr.Zero;
+ }
+ }
+
+ #endregion
+
+ #region 2. DBC 矩阵文件操作
+
+ ///
+ /// 指定通道加载特定的 DBC 文件(支持不同通道加载不同的DBC矩阵)
+ ///
+ public virtual bool 加载通道DBC文件(uint 通道号, string dbcFilePath)
+ {
+ if (通道号 >= _maxChannels) return false;
+
+ lock (_channelLocks[通道号])
+ {
+ try
+ {
+ if (_isDbcLoadedArray[通道号]) return true;
+
+ uint chDbcHandle = ZDBC.ZDBC_Init();
+ if (chDbcHandle == 0) return false;
+
+ IntPtr ptrPath = Marshal.StringToHGlobalAnsi(dbcFilePath);
+ bool success = ZDBC.ZDBC_LoadFile(chDbcHandle, ptrPath);
+ Marshal.FreeHGlobal(ptrPath);
+
+ if (success)
+ {
+ _dbcHandles[通道号] = chDbcHandle;
+ _isDbcLoadedArray[通道号] = true;
+ }
+ else
+ {
+ ZDBC.ZDBC_Release(chDbcHandle);
+ }
+ return success;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ }
+
+ public virtual void 释放通道DBC(uint 通道号)
+ {
+ if (通道号 >= _maxChannels) return;
+
+ lock (_channelLocks[通道号])
+ {
+ if (_isDbcLoadedArray[通道号] && _dbcHandles[通道号] != 0)
+ {
+ ZDBC.ZDBC_Release(_dbcHandles[通道号]);
+ _dbcHandles[通道号] = 0;
+ _isDbcLoadedArray[通道号] = false;
+ }
+ }
+ }
+
+ #endregion
+
+ #region 3. 轮询接收与实时自动化 DBC 解码
+
+ private void 接收轮询核心(IntPtr channelHandle, uint channelIndex)
+ {
+ const int bufferSize = 100;
+ int canStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Receive_Data));
+ int canfdStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_ReceiveFD_Data));
+ int dbcMsgSize = Marshal.SizeOf(typeof(ZDBC.DBCMessage));
+
+ IntPtr ptrCanBuffer = Marshal.AllocHGlobal(canStructSize * bufferSize);
+ IntPtr ptrCanFDBuffer = Marshal.AllocHGlobal(canfdStructSize * bufferSize);
+ IntPtr ptrDbcMsg = Marshal.AllocHGlobal(dbcMsgSize);
+
+ try
+ {
+ while (_isRunning)
+ {
+ bool currentTurnHasData = false;
+
+ // 1. 自动提取并解析经典 CAN 帧
+ uint canNum = ZLGCAN.ZCAN_GetReceiveNum(channelHandle, 0);
+ if (canNum > 0)
+ {
+ uint actualRecv = ZLGCAN.ZCAN_Receive(channelHandle, ptrCanBuffer, bufferSize, 5);
+ for (int i = 0; i < actualRecv; i++)
+ {
+ IntPtr framePtr = IntPtr.Add(ptrCanBuffer, i * canStructSize);
+
+ // 只锁定当前通道的DBC句柄进行解码,高并发完全不卡顿
+ lock (_channelLocks[channelIndex])
+ {
+ if (_isDbcLoadedArray[channelIndex] && ZDBC.ZDBC_Decode(_dbcHandles[channelIndex], ptrDbcMsg, framePtr, 1, 0))
+ {
+ var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
+ OnDbcMessageDecoded?.Invoke(channelIndex, msg);
+ }
+ }
+ }
+ currentTurnHasData = true;
+ }
+
+ // 2. 自动提取并解析高速 CANFD 帧
+ uint canfdNum = ZLGCAN.ZCAN_GetReceiveNum(channelHandle, 1);
+ if (canfdNum > 0)
+ {
+ uint actualRecvFd = ZLGCAN.ZCAN_ReceiveFD(channelHandle, ptrCanFDBuffer, bufferSize, 5);
+ for (int i = 0; i < actualRecvFd; i++)
+ {
+ IntPtr framePtr = IntPtr.Add(ptrCanFDBuffer, i * canfdStructSize);
+
+ lock (_channelLocks[channelIndex])
+ {
+ if (_isDbcLoadedArray[channelIndex] && ZDBC.ZDBC_Decode(_dbcHandles[channelIndex], ptrDbcMsg, framePtr, 1, 1))
+ {
+ var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
+ OnDbcMessageDecoded?.Invoke(channelIndex, msg);
+ }
+ }
+ }
+ currentTurnHasData = true;
+ }
+
+ if (!currentTurnHasData)
+ {
+ Thread.Sleep(2);
+ }
+ }
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(ptrCanBuffer);
+ Marshal.FreeHGlobal(ptrCanFDBuffer);
+ Marshal.FreeHGlobal(ptrDbcMsg);
+ }
+ }
+
+ #endregion
+
+ #region 4. DBC 自动打包与智能报文发送
+
+ ///
+ /// 发送一条 DBC 定义报文。
+ ///
+ /// 通道号
+ /// DBC 中定义的帧 ID
+ /// 信号名称 → 物理值;只设置字典中包含的信号,其余信号保持 DBC 默认值
+ /// 0 = 只发送一次;>0 = 按指定间隔循环发送(毫秒)
+ /// 1 = CANFD,0 = CAN
+ /// 是否成功启动/发送
+ public virtual bool 发送DBC定义报文(
+ uint 通道号,
+ uint 帧ID,
+ Dictionary 信号物理值字典,
+ int 循环间隔毫秒 = 0,
+ int 是否使用CANFD = 1)
+ {
+ if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
+ if (!_isDbcLoadedArray[通道号]) throw new InvalidOperationException($"通道 {通道号} 的 DBC 协议未加载,无法执行打包发送指令。");
+ if (循环间隔毫秒 < 0) throw new ArgumentOutOfRangeException(nameof(循环间隔毫秒), "循环间隔必须大于或等于 0。");
+ if (信号物理值字典 == null || 信号物理值字典.Count == 0)
+ throw new ArgumentException("信号物理值字典不能为空。", nameof(信号物理值字典));
+
+ if (循环间隔毫秒 == 0)
+ {
+ // 单次发送:同步执行
+ lock (_channelLocks[通道号])
+ {
+ return 发送DBC定义报文单次(通道号, 帧ID, 信号物理值字典, 是否使用CANFD);
+ }
+ }
+
+ // 循环发送:先停止同通道同帧 ID 的旧循环,再启动新循环
+ 停止循环发送(通道号, 帧ID);
+
+ var cts = new CancellationTokenSource();
+ _cyclicSenders[(通道号, 帧ID)] = cts;
+
+ Task.Run(async () =>
+ {
+ while (!cts.Token.IsCancellationRequested)
+ {
+ try
+ {
+ lock (_channelLocks[通道号])
+ {
+ if (_channelHandles[通道号] == IntPtr.Zero) break;
+ 发送DBC定义报文单次(通道号, 帧ID, 信号物理值字典, 是否使用CANFD);
+ }
+
+ await Task.Delay(循环间隔毫秒, cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ catch (Exception ex)
+ {
+ // 循环发送出错时退出,避免刷屏
+ Console.WriteLine($"[ZLGCANFD] 循环发送 DBC 报文失败: {ex.Message}");
+ break;
+ }
+ }
+
+ _cyclicSenders.TryRemove((通道号, 帧ID), out var removedCts);
+ removedCts?.Dispose();
+ }, cts.Token);
+
+ return true;
+ }
+
+ ///
+ /// 停止指定通道、指定帧 ID 的循环发送。
+ ///
+ public virtual void 停止循环发送(uint 通道号, uint 帧ID)
+ {
+ if (_cyclicSenders.TryRemove((通道号, 帧ID), out var cts))
+ {
+ cts.Cancel();
+ cts.Dispose();
+ }
+ }
+
+ ///
+ /// 停止所有 DBC 循环发送任务。
+ ///
+ public virtual void 停止所有循环发送()
+ {
+ foreach (var kvp in _cyclicSenders)
+ {
+ kvp.Value.Cancel();
+ kvp.Value.Dispose();
+ }
+ _cyclicSenders.Clear();
+ }
+
+
+
+ ///
+ /// 单次发送 DBC 报文(调用方已持有通道锁)。
+ ///
+ private bool 发送DBC定义报文单次(uint 通道号, uint 帧ID, Dictionary 信号物理值字典, int 是否使用CANFD)
+ {
+ uint chDbcHandle = _dbcHandles[通道号];
+
+ IntPtr ptrDbcMsg = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCMessage)));
+ IntPtr ptrCount = Marshal.AllocHGlobal(sizeof(uint));
+ Marshal.WriteInt32(ptrCount, 1);
+
+ try
+ {
+ if (!ZDBC.ZDBC_GetMessageById(chDbcHandle, 帧ID, ptrDbcMsg)) return false;
+ var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
+
+ // 根据传入的物理值设置对应信号的原始值
+ for (int i = 0; i < msg.nSignalCount; i++)
+ {
+ var signal = msg.vSignals[i];
+ string signalName = Encoding.Default.GetString(signal.strName).TrimEnd('\0');
+ if (信号物理值字典.TryGetValue(signalName, out double physicalValue))
+ {
+ signal.nRawvalue = 物理值转原始值(signal, physicalValue);
+ msg.vSignals[i] = signal;
+ }
+ }
+
+ Marshal.StructureToPtr(msg, ptrDbcMsg, true);
+
+ if (是否使用CANFD == 0)
+ {
+ IntPtr ptrCanFrame = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.can_frame)));
+ try
+ {
+ if (!ZDBC.ZDBC_Encode(chDbcHandle, ptrCanFrame, ptrCount, ptrDbcMsg, 0)) return false;
+
+ ZLGCAN.can_frame canFrame = (ZLGCAN.can_frame)Marshal.PtrToStructure(ptrCanFrame, typeof(ZLGCAN.can_frame));
+ canFrame.__pad |= 0x20;
+
+ ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = canFrame, transmit_type = 0 };
+ IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData));
+ try
+ {
+ Marshal.StructureToPtr(txData, pTx, true);
+ return ZLGCAN.ZCAN_Transmit(_channelHandles[通道号], pTx, 1) == 1;
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(pTx);
+ }
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(ptrCanFrame);
+ }
+ }
+ else
+ {
+ IntPtr ptrCanFDFrame = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.canfd_frame)));
+ try
+ {
+ if (!ZDBC.ZDBC_Encode(chDbcHandle, ptrCanFDFrame, ptrCount, ptrDbcMsg, 1)) return false;
+
+ ZLGCAN.canfd_frame canfdFrame = (ZLGCAN.canfd_frame)Marshal.PtrToStructure(ptrCanFDFrame, typeof(ZLGCAN.canfd_frame));
+ canfdFrame.flags |= 0x20;
+
+ ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = canfdFrame, transmit_type = 0 };
+ IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData));
+ try
+ {
+ Marshal.StructureToPtr(txFdData, pTxFd, true);
+ return ZLGCAN.ZCAN_TransmitFD(_channelHandles[通道号], pTxFd, 1) == 1;
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(pTxFd);
+ }
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(ptrCanFDFrame);
+ }
+ }
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(ptrDbcMsg);
+ Marshal.FreeHGlobal(ptrCount);
+ }
+ }
+
+ ///
+ /// 不依赖通道锁的物理值到原始值转换。
+ ///
+ private static ulong 物理值转原始值(ZDBC.DBCSignal 信号定义, double 实际物理值)
+ {
+ IntPtr pSignal = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCSignal)));
+ IntPtr pValue = Marshal.AllocHGlobal(sizeof(double));
+ try
+ {
+ Marshal.StructureToPtr(信号定义, pSignal, true);
+ Marshal.StructureToPtr(实际物理值, pValue, true);
+ return ZDBC.ZDBC_CalcRawValue(pSignal, pValue);
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(pSignal);
+ Marshal.FreeHGlobal(pValue);
+ }
+ }
+
+ #endregion
+
+ #region 4.1 仿同星高性能智能报文/信号控制流 (新增扩展)
+
+ ///
+ /// 设置并发送指定信号值。
+ /// 其余未指定信号使用 DBC 中定义的初始值填充,避免总线数据被意外清零。
+ ///
+ /// CAN/CANFD 通道索引
+ /// 对应报文的帧 ID
+ /// DBC 中定义的英文信号名称(不区分大小写)
+ /// 要写入的实际物理数值
+ /// 0 = 单次发送;>0 = 周期循环发送(单位毫秒)
+ /// 操作是否成功
+ public virtual bool 设置报文(uint 通道号, uint 帧ID, string 信号名称, double 物理值, int 循环发送间隔毫秒 = 0)
+ {
+ if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
+ if (!_isDbcLoadedArray[通道号]) return false;
+ if (string.IsNullOrWhiteSpace(信号名称)) return false;
+
+ var merged = 构建报文发送字典(通道号, 帧ID, new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ { 信号名称, 物理值 }
+ });
+
+ return 发送DBC定义报文(通道号, 帧ID, merged, 循环发送间隔毫秒);
+ }
+
+ ///
+ /// 发送报文:以 DBC 中所有信号的初始值作为默认值发送。
+ /// 如果之前调用 设置报文 保存过覆盖值,则会优先使用覆盖值。
+ ///
+ /// CAN/CANFD 通道索引
+ /// DBC 中定义的帧 ID
+ /// 0 = 单次发送;>0 = 周期循环发送(毫秒)
+ /// 1 = 以 CANFD 格式发送;0 = 经典 CAN 格式
+ public virtual bool 发送报文(uint 通道号, uint 帧ID, int 循环发送间隔毫秒 = 0, int 是否使用CANFD = 1)
+ {
+ if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
+ if (!_isDbcLoadedArray[通道号]) return false;
+ if (循环发送间隔毫秒 < 0) throw new ArgumentOutOfRangeException(nameof(循环发送间隔毫秒));
+
+ var merged = 构建报文发送字典(通道号, 帧ID);
+ return 发送DBC定义报文(通道号, 帧ID, merged, 循环发送间隔毫秒, 是否使用CANFD);
+ }
+
+ ///
+ /// 发送自定义报文(原始字节)。
+ ///
+ /// CAN/CANFD 通道索引
+ /// 帧 ID;扩展帧会自动置位 0x80000000
+ /// 原始帧数据
+ /// 数据长度;CANFD 仅允许 0-8,12,16,20,24,32,48,64
+ /// 是否为 29 位扩展帧
+ /// true = CANFD;false = 经典 CAN
+ /// CANFD 是否开启波特率加速
+ public virtual bool 发送自定义报文(
+ uint 通道号,
+ uint 帧ID,
+ byte[] 原始数据,
+ byte dlcLength,
+ bool 是否是扩展帧 = false,
+ bool 是否是CANFD = true,
+ bool 开启波特率加速BRS = true)
+ {
+ if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
+ if (原始数据 == null) return false;
+
+ // 扩展帧需要把最高位标志位置起来
+ uint canId = 帧ID & 0x7FFFFFFF;
+ if (是否是扩展帧) canId |= 0x80000000;
+
+ lock (_channelLocks[通道号])
+ {
+ if (是否是CANFD)
+ {
+ // CANFD 有效 DLC 校验
+ byte validFdDlc = 校验CANFD的Dlc(dlcLength);
+
+ ZLGCAN.canfd_frame fdFrame = new ZLGCAN.canfd_frame
+ {
+ can_id = canId,
+ len = validFdDlc,
+ flags = (byte)((是否是扩展帧 ? 0x01 : 0x00) | (开启波特率加速BRS ? 0x02 : 0x00) | 0x20), // 0x20 本地回显
+ data = new byte[64]
+ };
+ Array.Copy(原始数据, 0, fdFrame.data, 0, Math.Min(原始数据.Length, 64));
+
+ ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = fdFrame, transmit_type = 0 };
+ IntPtr pTxFd = Marshal.AllocHGlobal(Marshal.SizeOf(txFdData));
+ try
+ {
+ Marshal.StructureToPtr(txFdData, pTxFd, true);
+ return ZLGCAN.ZCAN_TransmitFD(_channelHandles[通道号], pTxFd, 1) == 1;
+ }
+ finally { Marshal.FreeHGlobal(pTxFd); }
+ }
+ else
+ {
+ ZLGCAN.can_frame standardFrame = new ZLGCAN.can_frame
+ {
+ can_id = canId,
+ can_dlc = dlcLength > 8 ? (byte)8 : dlcLength,
+ __pad = 0x20, // 发送回显
+ data = new byte[8]
+ };
+ Array.Copy(原始数据, 0, standardFrame.data, 0, Math.Min(原始数据.Length, 8));
+
+ ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = standardFrame, transmit_type = 0 };
+ IntPtr pTx = Marshal.AllocHGlobal(Marshal.SizeOf(txData));
+ try
+ {
+ Marshal.StructureToPtr(txData, pTx, true);
+ return ZLGCAN.ZCAN_Transmit(_channelHandles[通道号], pTx, 1) == 1;
+ }
+ finally { Marshal.FreeHGlobal(pTx); }
+ }
+ }
+ }
+
+ ///
+ /// 构建发送用信号字典:DBC 初始值 + 额外覆盖。
+ /// 调用方需自行保证通道已初始化且 DBC 已加载。
+ ///
+ private Dictionary 构建报文发送字典(uint 通道号, uint 帧ID, Dictionary? 额外覆盖 = null)
+ {
+ uint chDbcHandle = _dbcHandles[通道号];
+ IntPtr ptrDbcMsg = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCMessage)));
+ try
+ {
+ lock (_channelLocks[通道号])
+ {
+ if (!ZDBC.ZDBC_GetMessageById(chDbcHandle, 帧ID, ptrDbcMsg))
+ return new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
+ var dict = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ for (int i = 0; i < msg.nSignalCount; i++)
+ {
+ var signal = msg.vSignals[i];
+ string signalName = Encoding.Default.GetString(signal.strName).TrimEnd('\0');
+ if (!string.IsNullOrEmpty(signalName) && signal.initialValueValid != 0)
+ {
+ // ZDBC 文档标注 initialValue 为“原始值”,但实际按 DBC 规范这里存放的是物理值
+ dict[signalName] = signal.initialValue;
+ }
+ }
+
+ if (额外覆盖 != null)
+ {
+ foreach (var kvp in 额外覆盖)
+ {
+ dict[kvp.Key] = kvp.Value;
+ }
+ }
+
+ return dict;
+ }
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(ptrDbcMsg);
+ }
+ }
+
+ ///
+ /// CANFD 有效 DLC 校验,非法值自动就近取到下一个合法 DLC。
+ ///
+ private static byte 校验CANFD的Dlc(byte dlc)
+ {
+ if (dlc <= 8) return dlc;
+ if (dlc <= 12) return 12;
+ if (dlc <= 16) return 16;
+ if (dlc <= 20) return 20;
+ if (dlc <= 24) return 24;
+ if (dlc <= 32) return 32;
+ if (dlc <= 48) return 48;
+ return 64;
+ }
+
+ #endregion
+
+ #region 5. 辅助工具子系统
+
+ ///
+ /// 计算物理值也需要传入对应通道,使用通道专属的DBC句柄进行计算
+ ///
+
+ [Browsable(false)]
+ public ulong 物理值转原始寄存器值(uint 通道号, ZDBC.DBCSignal 信号定义, double 实际物理值)
+ {
+ if (通道号 >= _maxChannels) return 0;
+
+ IntPtr pSignal = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZDBC.DBCSignal)));
+ IntPtr pValue = Marshal.AllocHGlobal(sizeof(double));
+
+ try
+ {
+ Marshal.StructureToPtr(信号定义, pSignal, true);
+ Marshal.StructureToPtr(实际物理值, pValue, true);
+ lock (_channelLocks[通道号])
+ {
+ return ZDBC.ZDBC_CalcRawValue(pSignal, pValue);
+ }
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(pSignal);
+ Marshal.FreeHGlobal(pValue);
+ }
+ }
+
+ #endregion
+
+ [Browsable(false)]
+ public void Dispose()
+ {
+ 关闭CAN卡设备();
+ }
+ }
+}
\ No newline at end of file
diff --git a/USBCANFD/ZLGAPI.cs b/ZLGUSBCANFD/ZLGAPI.cs
similarity index 99%
rename from USBCANFD/ZLGAPI.cs
rename to ZLGUSBCANFD/ZLGAPI.cs
index bf1af57..938a041 100644
--- a/USBCANFD/ZLGAPI.cs
+++ b/ZLGUSBCANFD/ZLGAPI.cs
@@ -1,7 +1,7 @@
// update time 2025/7/16
using System.Runtime.InteropServices;
-namespace USBCANFD
+namespace ZLGUSBCANFD
{
public class ZLGCAN
diff --git a/USBCANFD/USBCANFD.csproj b/ZLGUSBCANFD/ZLGUSBCANFD.csproj
similarity index 100%
rename from USBCANFD/USBCANFD.csproj
rename to ZLGUSBCANFD/ZLGUSBCANFD.csproj