CAN设置添加
This commit is contained in:
10
ADP.sln
10
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
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<ProjectReference Include="..\TestingModule\TestingModule.csproj" />
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
<ProjectReference Include="..\UpdateInfoMoudle\UpdateInfoMoudle.csproj" />
|
||||
<ProjectReference Include="..\USBCANFD\USBCANFD.csproj" />
|
||||
<ProjectReference Include="..\ZLGUSBCANFD\ZLGUSBCANFD.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace SettingModule
|
||||
// 设备连接配置弹窗
|
||||
containerRegistry.RegisterDialog<TCPConfigView>("TCPConfig");
|
||||
containerRegistry.RegisterDialog<SerialPortConfigView>("SerialPortConfig");
|
||||
containerRegistry.RegisterDialog<CANConfigView>("CANConfig");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
134
SettingModule/ViewModels/Dialogs/CANConfigViewModel.cs
Normal file
134
SettingModule/ViewModels/Dialogs/CANConfigViewModel.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// CAN 连接配置对话框 VM。
|
||||
/// 通过 DialogParameters 接收宿主 DeviceInfoVM;保存时把副本写回宿主。
|
||||
/// 参数对应 ZLGCANFD 构造函数 + 初始化并启动通道 方法。
|
||||
/// </summary>
|
||||
public class CANConfigViewModel : DialogViewModelBase
|
||||
{
|
||||
#region 属性
|
||||
|
||||
private string _title = "CAN 连接配置";
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
/// <summary>编辑用的副本,取消时不会污染宿主对象。</summary>
|
||||
private CANConfigVM _config = new();
|
||||
public CANConfigVM Config
|
||||
{
|
||||
get => _config;
|
||||
set => SetProperty(ref _config, value);
|
||||
}
|
||||
|
||||
/// <summary>常用仲裁域波特率。</summary>
|
||||
public ObservableCollection<string> CommonABitBauds { get; } = new()
|
||||
{
|
||||
"250000", "500000", "1000000"
|
||||
};
|
||||
|
||||
/// <summary>常用数据域波特率。</summary>
|
||||
public ObservableCollection<string> 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<OverlayEvent>().Publish(true);
|
||||
|
||||
if (parameters.ContainsKey("Device"))
|
||||
{
|
||||
_hostDevice = parameters.GetValue<DeviceInfoVM>("Device");
|
||||
Title = $"CAN 连接配置 - {_hostDevice?.DeviceName}";
|
||||
Config = new CANConfigVM(_hostDevice?.CANConfig);
|
||||
}
|
||||
else if (parameters.ContainsKey("Config"))
|
||||
{
|
||||
Config = new CANConfigVM(parameters.GetValue<CANConfigVM>("Config"));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDialogClosed()
|
||||
{
|
||||
_eventAggregator.GetEvent<OverlayEvent>().Publish(false);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -59,10 +59,9 @@ namespace SettingModule.ViewModels
|
||||
|
||||
public ObservableCollection<string> 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<SystemConfig>();
|
||||
ConfigService.EnsureDefaultCanDevice(SystemConfig);
|
||||
if (DeviceList != null && DeviceList.Count > 0)
|
||||
{
|
||||
SelectedDevice = DeviceList[0];
|
||||
|
||||
131
SettingModule/Views/Dialogs/CANConfigView.xaml
Normal file
131
SettingModule/Views/Dialogs/CANConfigView.xaml
Normal file
@@ -0,0 +1,131 @@
|
||||
<UserControl x:Class="SettingModule.Views.Dialogs.CANConfigView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:SettingModule.Views.Dialogs"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:helpers="clr-namespace:UIShare.Helpers;assembly=UIShare"
|
||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
mc:Ignorable="d"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
Background="White"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
Width="440"
|
||||
Height="360">
|
||||
<prism:Dialog.WindowStyle>
|
||||
<Style BasedOn="{StaticResource DialogUserManageStyle}"
|
||||
TargetType="Window" />
|
||||
</prism:Dialog.WindowStyle>
|
||||
|
||||
<UserControl.Resources>
|
||||
<converters:StringToVisibilityConverter x:Key="StringToVisibility"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<GroupBox Padding="12,8,12,8"
|
||||
helpers:WindowDragHelper.EnableWindowDrag="True">
|
||||
<GroupBox.Header>
|
||||
<Grid Margin="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding Title}"
|
||||
Foreground="White"
|
||||
VerticalAlignment="Center"
|
||||
Margin="5,0,10,0" />
|
||||
</Grid>
|
||||
</GroupBox.Header>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 表单 -->
|
||||
<Grid Grid.Row="0" Margin="0,4,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 设备类型号 -->
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
Text="设备类型号:"
|
||||
VerticalAlignment="Center" Margin="0,6"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Margin="0,6"
|
||||
materialDesign:HintAssist.Hint="43 = USBCANFD-400U"
|
||||
Text="{Binding Config.DeviceType, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
|
||||
<!-- 设备索引 -->
|
||||
<TextBlock Grid.Row="1" Grid.Column="0"
|
||||
Text="设备索引:"
|
||||
VerticalAlignment="Center" Margin="0,6"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" Margin="0,6"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding Config.DeviceIndex, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
|
||||
<!-- 仲裁域波特率 -->
|
||||
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||
Text="仲裁域波特率:"
|
||||
VerticalAlignment="Center" Margin="0,6"/>
|
||||
<ComboBox Grid.Row="2" Grid.Column="1" Margin="0,6"
|
||||
IsEditable="True"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
ItemsSource="{Binding CommonABitBauds}"
|
||||
Text="{Binding Config.ABitBaud, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
|
||||
<!-- 数据域波特率 -->
|
||||
<TextBlock Grid.Row="3" Grid.Column="0"
|
||||
Text="数据域波特率:"
|
||||
VerticalAlignment="Center" Margin="0,6"/>
|
||||
<ComboBox Grid.Row="3" Grid.Column="1" Margin="0,6"
|
||||
IsEditable="True"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
ItemsSource="{Binding CommonDBitBauds}"
|
||||
Text="{Binding Config.DBitBaud, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
|
||||
<!-- 终端电阻 -->
|
||||
<TextBlock Grid.Row="4" Grid.Column="0"
|
||||
Text="终端电阻:"
|
||||
VerticalAlignment="Center" Margin="0,6"/>
|
||||
<ToggleButton Grid.Row="4" Grid.Column="1" Margin="0,6"
|
||||
IsChecked="{Binding Config.EnableTerminalResistance}"
|
||||
Style="{StaticResource MaterialDesignSwitchToggleButton}"/>
|
||||
</Grid>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<TextBlock Grid.Row="1"
|
||||
Margin="0,8,0,0"
|
||||
Foreground="#D32F2F"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding ErrorMessage}"
|
||||
Visibility="{Binding ErrorMessage, Converter={StaticResource StringToVisibility}}"/>
|
||||
|
||||
<!-- 按钮 -->
|
||||
<StackPanel Grid.Row="2"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,12,0,0">
|
||||
<Button Content="取消"
|
||||
Width="80" Padding="0,4"
|
||||
Command="{Binding CancelCommand}"/>
|
||||
<Button Content="保存"
|
||||
Width="80" Padding="0,4"
|
||||
Margin="10,0,0,0"
|
||||
IsDefault="True"
|
||||
Command="{Binding SaveCommand}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</UserControl>
|
||||
15
SettingModule/Views/Dialogs/CANConfigView.xaml.cs
Normal file
15
SettingModule/Views/Dialogs/CANConfigView.xaml.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace SettingModule.Views.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// CANConfigView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class CANConfigView : UserControl
|
||||
{
|
||||
public CANConfigView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -368,6 +368,33 @@
|
||||
<Run Text="{Binding SelectedDevice.SerialPortConfig.Parity}"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<!-- CAN 参数预览 -->
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"
|
||||
Margin="0,4,0,0" Orientation="Horizontal">
|
||||
<StackPanel.Style>
|
||||
<Style TargetType="StackPanel">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding SelectedDevice.ConnectionType}" Value="CAN">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</StackPanel.Style>
|
||||
<TextBlock Foreground="#666" FontSize="12">
|
||||
<Run Text="设备类型:"/>
|
||||
<Run Text="{Binding SelectedDevice.CANConfig.DeviceType}"/>
|
||||
<Run Text=" 设备索引:"/>
|
||||
<Run Text="{Binding SelectedDevice.CANConfig.DeviceIndex}"/>
|
||||
<Run Text=" 仲裁/数据波特率:"/>
|
||||
<Run Text="{Binding SelectedDevice.CANConfig.ABitBaud}"/>
|
||||
<Run Text="/"/>
|
||||
<Run Text="{Binding SelectedDevice.CANConfig.DBitBaud}"/>
|
||||
<Run Text=" 终端电阻:"/>
|
||||
<Run Text="{Binding SelectedDevice.CANConfig.EnableTerminalResistance}"/>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保配置中至少包含一个 CAN 设备(对应 SystemConfig.CANFD)。
|
||||
/// 旧配置或空配置会自动升级,使用户在设置界面能看到 CAN 设备。
|
||||
/// </summary>
|
||||
public static void EnsureDefaultCanDevice(SystemConfig config)
|
||||
{
|
||||
if (config.DeviceList == null)
|
||||
{
|
||||
config.DeviceList = new ObservableCollection<DeviceInfoVM>();
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
/// <summary>类名 → Type 的反射缓存(仅扫描一次)。</summary>
|
||||
private static readonly IReadOnlyDictionary<string, Type> _deviceTypeMap = BuildDeviceTypeMap();
|
||||
public ZLGCANFD CANFD { get; set; }
|
||||
|
||||
public DeviceManager(SystemConfig systemConfig, GlobalInfo globalInfo)
|
||||
{
|
||||
@@ -37,6 +39,7 @@ namespace UIShare.GlobalVariable
|
||||
_scopeName = _systemConfig.Title;
|
||||
InitDevices();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据设备配置提取唯一的硬件指纹字符串。
|
||||
/// <para>Tcp → "Tcp:IP:Port";Serial → "Serial:PortName";无法识别则返回空字符串。</para>
|
||||
@@ -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<string, IBaseInterface>(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<ZLGCANFD>(() =>
|
||||
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<List<string>>(() => new List<string>())).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 _);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// <summary>硬件指纹 → 设备实例的并发池,确保同一物理硬件全局只创建一个驱动实例。</summary>
|
||||
public ConcurrentDictionary<string, Lazy<IBaseInterface>> HardwarePool { get; set; }
|
||||
|
||||
/// <summary>CAN 硬件指纹 → ZLGCANFD 实例的并发池,确保同一 CAN 卡全局只创建一个驱动实例。</summary>
|
||||
public ConcurrentDictionary<string, Lazy<ZLGCANFD>> CanPool { get; set; }
|
||||
|
||||
/// <summary>硬件指纹 → 正在使用该设备的作用域名称列表,用于引用计数与安全销毁。</summary>
|
||||
public ConcurrentDictionary<string, Lazy<List<string>>> DeviceAndScopeDic { get; set; }
|
||||
|
||||
@@ -45,6 +49,7 @@ namespace UIShare.GlobalVariable
|
||||
ConfigDic = new();
|
||||
ScopeDic = new();
|
||||
HardwarePool = new ConcurrentDictionary<string, Lazy<IBaseInterface>>(StringComparer.OrdinalIgnoreCase);
|
||||
CanPool = new ConcurrentDictionary<string, Lazy<ZLGCANFD>>(StringComparer.OrdinalIgnoreCase);
|
||||
DeviceAndScopeDic = new ConcurrentDictionary<string, Lazy<List<string>>>(StringComparer.OrdinalIgnoreCase);
|
||||
CurrentScope = "default";
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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<DeviceInfoVM> DeviceList = new();
|
||||
public ObservableCollection<SharedParameter> SharedParameterList = new();
|
||||
public ZLGCANFD CANFD = new();
|
||||
[JsonIgnore]
|
||||
public ObservableCollection<ParameterVM> ParameterList = new()
|
||||
{
|
||||
|
||||
@@ -20,5 +20,6 @@
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
<ProjectReference Include="..\DeviceCommand\DeviceCommand.csproj" />
|
||||
<ProjectReference Include="..\Logger\Logger.csproj" />
|
||||
<ProjectReference Include="..\ZLGUSBCANFD\ZLGUSBCANFD.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
76
UIShare/UIViewModel/CANConfigVM.cs
Normal file
76
UIShare/UIViewModel/CANConfigVM.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using Prism.Mvvm;
|
||||
|
||||
namespace UIShare.UIViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// CAN 连接配置(对应 ZLGCANFD 构造函数 + 初始化并启动通道 参数)。
|
||||
/// </summary>
|
||||
public class CANConfigVM : BindableBase
|
||||
{
|
||||
// ===== ZLGCANFD 构造函数参数 =====
|
||||
|
||||
private uint _deviceType = 43;
|
||||
/// <summary>设备类型号(43 = USBCANFD-400U)</summary>
|
||||
public uint DeviceType
|
||||
{
|
||||
get => _deviceType;
|
||||
set => SetProperty(ref _deviceType, value);
|
||||
}
|
||||
|
||||
private uint _deviceIndex = 0;
|
||||
/// <summary>设备索引</summary>
|
||||
public uint DeviceIndex
|
||||
{
|
||||
get => _deviceIndex;
|
||||
set => SetProperty(ref _deviceIndex, value);
|
||||
}
|
||||
|
||||
private string _abitBaud = "500000";
|
||||
/// <summary>仲裁域波特率</summary>
|
||||
public string ABitBaud
|
||||
{
|
||||
get => _abitBaud;
|
||||
set => SetProperty(ref _abitBaud, value);
|
||||
}
|
||||
|
||||
private string _dbitBaud = "2000000";
|
||||
/// <summary>数据域波特率</summary>
|
||||
public string DBitBaud
|
||||
{
|
||||
get => _dbitBaud;
|
||||
set => SetProperty(ref _dbitBaud, value);
|
||||
}
|
||||
|
||||
private bool _enableTerminalResistance = true;
|
||||
/// <summary>是否开启终端电阻</summary>
|
||||
public bool EnableTerminalResistance
|
||||
{
|
||||
get => _enableTerminalResistance;
|
||||
set => SetProperty(ref _enableTerminalResistance, value);
|
||||
}
|
||||
|
||||
public CANConfigVM() { }
|
||||
|
||||
/// <summary>拷贝构造,用于对话框编辑副本。</summary>
|
||||
public CANConfigVM(CANConfigVM? src)
|
||||
{
|
||||
if (src == null) return;
|
||||
DeviceType = src.DeviceType;
|
||||
DeviceIndex = src.DeviceIndex;
|
||||
ABitBaud = src.ABitBaud;
|
||||
DBitBaud = src.DBitBaud;
|
||||
EnableTerminalResistance = src.EnableTerminalResistance;
|
||||
}
|
||||
|
||||
/// <summary>把字段拷回目标对象(保存时用)。</summary>
|
||||
public void CopyTo(CANConfigVM? dst)
|
||||
{
|
||||
if (dst == null) return;
|
||||
dst.DeviceType = DeviceType;
|
||||
dst.DeviceIndex = DeviceIndex;
|
||||
dst.ABitBaud = ABitBaud;
|
||||
dst.DBitBaud = DBitBaud;
|
||||
dst.EnableTerminalResistance = EnableTerminalResistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,5 +65,13 @@ namespace UIShare.UIViewModel
|
||||
get => _serialPortConfig;
|
||||
set => SetProperty(ref _serialPortConfig, value);
|
||||
}
|
||||
|
||||
/// <summary>CAN 连接参数(对应 ZLGCANFD 构造 + 通道初始化参数)。</summary>
|
||||
private CANConfigVM _canConfig = new();
|
||||
public CANConfigVM CANConfig
|
||||
{
|
||||
get => _canConfig;
|
||||
set => SetProperty(ref _canConfig, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Thread> _receiveThreads = new List<Thread>();
|
||||
|
||||
// 动态硬件参数
|
||||
private readonly uint _deviceType; // 43: USBCANFD-400U
|
||||
private readonly uint _deviceIndex; // 设备索引
|
||||
private readonly int _maxChannels; // 动态通道数
|
||||
|
||||
/// <summary>
|
||||
/// 事件:当接收到 CAN/CANFD 报文并且通过 DBC 成功解析后触发
|
||||
/// 参数:uint 通道号 (0,1,2,3), ZDBC.DBCMessage 解析后的DBC消息结构体
|
||||
/// </summary>
|
||||
public event Action<uint, ZDBC.DBCMessage>? 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. 硬件连接与通道初始化
|
||||
|
||||
/// <summary>
|
||||
/// 仅仅打开设备,不做具体的通道波特率配置(留给具体的台架去分别配置)
|
||||
/// </summary>
|
||||
public virtual bool 打开设备()
|
||||
{
|
||||
if (_deviceHandle != IntPtr.Zero) return true;
|
||||
_deviceHandle = ZLGCAN.ZCAN_OpenDevice(_deviceType, _deviceIndex, 0);
|
||||
return _deviceHandle != IntPtr.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 【核心修改】针对特定通道进行参数初始化并启动
|
||||
/// </summary>
|
||||
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 矩阵文件操作
|
||||
|
||||
/// <summary>
|
||||
/// 【核心修改】指定通道加载特定的 DBC 文件(支持不同通道加载不同的DBC矩阵)
|
||||
/// </summary>
|
||||
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<ZDBC.DBCMessage> 配置信号动作, 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. 辅助工具子系统
|
||||
|
||||
/// <summary>
|
||||
/// 【核心修改】计算物理值也需要传入对应通道,使用通道专属的DBC句柄进行计算
|
||||
/// </summary>
|
||||
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卡设备();
|
||||
}
|
||||
}
|
||||
}
|
||||
729
ZLGUSBCANFD/USBCANFD.cs
Normal file
729
ZLGUSBCANFD/USBCANFD.cs
Normal file
@@ -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<Thread> _receiveThreads = new List<Thread>();
|
||||
|
||||
// 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; // 动态通道数
|
||||
|
||||
/// <summary>
|
||||
/// 事件:当接收到 CAN/CANFD 报文并且通过 DBC 成功解析后触发
|
||||
/// 参数:uint 通道号 (0,1,2,3), ZDBC.DBCMessage 解析后的DBC消息结构体
|
||||
/// </summary>
|
||||
public event Action<uint, ZDBC.DBCMessage>? 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. 硬件连接与通道初始化
|
||||
|
||||
/// <summary>
|
||||
/// 仅仅打开设备,不做具体的通道波特率配置(留给具体的台架去分别配置)
|
||||
/// </summary>
|
||||
public virtual bool 打开设备()
|
||||
{
|
||||
if (_deviceHandle != IntPtr.Zero) return true;
|
||||
_deviceHandle = ZLGCAN.ZCAN_OpenDevice(ZLGCAN.ZCAN_USBCANFD_400U, _deviceIndex, 0);
|
||||
return _deviceHandle != IntPtr.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 针对特定通道进行参数初始化并启动
|
||||
/// </summary>
|
||||
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 矩阵文件操作
|
||||
|
||||
/// <summary>
|
||||
/// 指定通道加载特定的 DBC 文件(支持不同通道加载不同的DBC矩阵)
|
||||
/// </summary>
|
||||
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 自动打包与智能报文发送
|
||||
|
||||
/// <summary>
|
||||
/// 发送一条 DBC 定义报文。
|
||||
/// </summary>
|
||||
/// <param name="通道号">通道号</param>
|
||||
/// <param name="帧ID">DBC 中定义的帧 ID</param>
|
||||
/// <param name="信号物理值字典">信号名称 → 物理值;只设置字典中包含的信号,其余信号保持 DBC 默认值</param>
|
||||
/// <param name="循环间隔毫秒">0 = 只发送一次;>0 = 按指定间隔循环发送(毫秒)</param>
|
||||
/// <param name="是否使用CANFD">1 = CANFD,0 = CAN</param>
|
||||
/// <returns>是否成功启动/发送</returns>
|
||||
public virtual bool 发送DBC定义报文(
|
||||
uint 通道号,
|
||||
uint 帧ID,
|
||||
Dictionary<string, double> 信号物理值字典,
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止指定通道、指定帧 ID 的循环发送。
|
||||
/// </summary>
|
||||
public virtual void 停止循环发送(uint 通道号, uint 帧ID)
|
||||
{
|
||||
if (_cyclicSenders.TryRemove((通道号, 帧ID), out var cts))
|
||||
{
|
||||
cts.Cancel();
|
||||
cts.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止所有 DBC 循环发送任务。
|
||||
/// </summary>
|
||||
public virtual void 停止所有循环发送()
|
||||
{
|
||||
foreach (var kvp in _cyclicSenders)
|
||||
{
|
||||
kvp.Value.Cancel();
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
_cyclicSenders.Clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 单次发送 DBC 报文(调用方已持有通道锁)。
|
||||
/// </summary>
|
||||
private bool 发送DBC定义报文单次(uint 通道号, uint 帧ID, Dictionary<string, double> 信号物理值字典, 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 不依赖通道锁的物理值到原始值转换。
|
||||
/// </summary>
|
||||
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 仿同星高性能智能报文/信号控制流 (新增扩展)
|
||||
|
||||
/// <summary>
|
||||
/// 设置并发送指定信号值。
|
||||
/// 其余未指定信号使用 DBC 中定义的初始值填充,避免总线数据被意外清零。
|
||||
/// </summary>
|
||||
/// <param name="通道号">CAN/CANFD 通道索引</param>
|
||||
/// <param name="帧ID">对应报文的帧 ID</param>
|
||||
/// <param name="信号名称">DBC 中定义的英文信号名称(不区分大小写)</param>
|
||||
/// <param name="物理值">要写入的实际物理数值</param>
|
||||
/// <param name="循环发送间隔毫秒">0 = 单次发送;>0 = 周期循环发送(单位毫秒)</param>
|
||||
/// <returns>操作是否成功</returns>
|
||||
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<string, double>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ 信号名称, 物理值 }
|
||||
});
|
||||
|
||||
return 发送DBC定义报文(通道号, 帧ID, merged, 循环发送间隔毫秒);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送报文:以 DBC 中所有信号的初始值作为默认值发送。
|
||||
/// 如果之前调用 设置报文 保存过覆盖值,则会优先使用覆盖值。
|
||||
/// </summary>
|
||||
/// <param name="通道号">CAN/CANFD 通道索引</param>
|
||||
/// <param name="帧ID">DBC 中定义的帧 ID</param>
|
||||
/// <param name="循环发送间隔毫秒">0 = 单次发送;>0 = 周期循环发送(毫秒)</param>
|
||||
/// <param name="是否使用CANFD">1 = 以 CANFD 格式发送;0 = 经典 CAN 格式</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送自定义报文(原始字节)。
|
||||
/// </summary>
|
||||
/// <param name="通道号">CAN/CANFD 通道索引</param>
|
||||
/// <param name="帧ID">帧 ID;扩展帧会自动置位 0x80000000</param>
|
||||
/// <param name="原始数据">原始帧数据</param>
|
||||
/// <param name="dlcLength">数据长度;CANFD 仅允许 0-8,12,16,20,24,32,48,64</param>
|
||||
/// <param name="是否是扩展帧">是否为 29 位扩展帧</param>
|
||||
/// <param name="是否是CANFD">true = CANFD;false = 经典 CAN</param>
|
||||
/// <param name="开启波特率加速BRS">CANFD 是否开启波特率加速</param>
|
||||
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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建发送用信号字典:DBC 初始值 + 额外覆盖。
|
||||
/// 调用方需自行保证通道已初始化且 DBC 已加载。
|
||||
/// </summary>
|
||||
private Dictionary<string, double> 构建报文发送字典(uint 通道号, uint 帧ID, Dictionary<string, double>? 额外覆盖 = 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<string, double>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var msg = (ZDBC.DBCMessage)Marshal.PtrToStructure(ptrDbcMsg, typeof(ZDBC.DBCMessage));
|
||||
var dict = new Dictionary<string, double>(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CANFD 有效 DLC 校验,非法值自动就近取到下一个合法 DLC。
|
||||
/// </summary>
|
||||
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. 辅助工具子系统
|
||||
|
||||
/// <summary>
|
||||
/// 计算物理值也需要传入对应通道,使用通道专属的DBC句柄进行计算
|
||||
/// </summary>
|
||||
|
||||
[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卡设备();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// update time 2025/7/16
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace USBCANFD
|
||||
namespace ZLGUSBCANFD
|
||||
{
|
||||
|
||||
public class ZLGCAN
|
||||
Reference in New Issue
Block a user