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/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