Files
ADP/ZLGUSBCANFD/USBCANFD.cs
T
2026-08-13 13:35:43 +08:00

1000 lines
43 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Common.Attributes;
using Logger;
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 volatile bool _isClosing = false; // 关闭流程标志:通知循环发送任务尽快退出
private readonly List<Thread> _receiveThreads = new List<Thread>();
// DBC 循环发送任务管理:Key = (通道号, 帧ID)Value = (CTS, Task)
private readonly ConcurrentDictionary<(uint 通道号, uint ID), (CancellationTokenSource Cts, Task SendTask)> _cyclicSenders = new ConcurrentDictionary<(uint, uint), (CancellationTokenSource, Task)>();
// 信号值持久化覆盖表:Key = (通道号, 帧ID),Value = 信号名 → 物理值
// 每次 设置报文 会累积写入此表,发送时以 DBC 初始值为底再叠加此表覆盖值
private readonly ConcurrentDictionary<(uint 通道号, uint ID), Dictionary<string, double>> _signalOverrides = new ConcurrentDictionary<(uint, uint), Dictionary<string, double>>();
// 动态硬件参数
private readonly uint _deviceType; // 76: USBCANFD-400U
private readonly uint _deviceIndex; // 设备索引
private readonly int _maxChannels; // 动态通道数
// 通道波特率与终端电阻配置(构造时传入,所有通道共用)
private readonly string _abitBaud;
private readonly string _dbitBaud;
private readonly bool _enableTerminalResistance;
/// <summary>
/// 当前 CAN 卡专属的 DBC 解析器,按通道隔离报文数据库。
/// </summary>
public ZLGDBCParser DBCParser { get; }
/// <summary>
/// 事件:当接收到 CAN/CANFD 报文并且通过 DBC 成功解析后触发
/// 参数:uint 通道号 (0,1,2,3), ZDBC.DBCMessage 解析后的DBC消息结构体
/// </summary>
public event Action<uint, ZDBC.DBCMessage>? OnDbcMessageDecoded;
/// <summary>
/// 事件:当接收到任意 CAN/CANFD 原始报文时触发(不依赖 DBC,自定义报文也能收到)
/// 参数:通道号, 帧ID, 数据, 时间戳(微秒), 是否FD, 数据长度
/// </summary>
public event Action<uint, uint, byte[], ulong, bool, byte>? OnRawMessageReceived;
public ZLGCANFD(uint deviceType = 76, uint deviceIndex = 0, int maxChannels = 4,
string abitBaud = "500000", string dbitBaud = "2000000", bool enableTerminalResistance = true)
{
_deviceType = deviceType;
_deviceIndex = deviceIndex;
_maxChannels = maxChannels;
_abitBaud = abitBaud;
_dbitBaud = dbitBaud;
_enableTerminalResistance = enableTerminalResistance;
// 初始化通道相关状态数组
_channelHandles = new IntPtr[_maxChannels];
_dbcHandles = new uint[_maxChannels];
_isDbcLoadedArray = new bool[_maxChannels];
_channelLocks = new object[_maxChannels];
DBCParser = new ZLGDBCParser(_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 通道号)
{
_isClosing = false; // 重置关闭标志,允许循环发送
if (_deviceHandle == IntPtr.Zero) throw new InvalidOperationException("请先调用 '打开设备()' 才能初始化通道。");
if (通道号 >= _maxChannels) return false;
lock (_channelLocks[通道号])
{
// 如果已经启动过,直接退出
if (_channelHandles[通道号] != IntPtr.Zero)
{
return true;
//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 = _enableTerminalResistance ? "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卡设备()
{
_isClosing = true; // 通知循环发送任务尽快退出
_isRunning = false; // 通知接收轮询线程退出
停止所有循环发送(); // 内部会等待所有循环发送 Task 退出
// 等待所有接收轮询线程真正退出(每个最多 1 秒)
foreach (var thread in _receiveThreads)
{
if (thread.IsAlive)
thread.Join(1000);
}
_receiveThreads.Clear();
// 动态复位所有通道并释放 DBC(使用 TryEnter 防止死锁)
for (uint i = 0; i < _maxChannels; i++)
{
if (Monitor.TryEnter(_channelLocks[i], TimeSpan.FromSeconds(2)))
{
try
{
if (_channelHandles[i] != IntPtr.Zero)
{
ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]);
_channelHandles[i] = IntPtr.Zero;
}
释放通道DBC(i);
}
finally
{
Monitor.Exit(_channelLocks[i]);
}
}
else
{
// 锁超时:循环发送 Task 可能仍阻塞在原生 API 调用中,强制清理
LoggerHelper.Info($"[ZLGCANFD] 关闭通道 {i} 时获取锁超时,强制清理");
if (_channelHandles[i] != IntPtr.Zero)
{
try { ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]); } catch { }
_channelHandles[i] = IntPtr.Zero;
}
释放通道DBC(i);
}
}
// 关闭设备主句柄
if (_deviceHandle != IntPtr.Zero)
{
ZLGCAN.ZCAN_CloseDevice(_deviceHandle);
_deviceHandle = IntPtr.Zero;
}
_isClosing = false;
}
#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;
//加载前先卸载
释放通道DBC(通道号);
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;
DBCParser.ParseAndLoadToDatabase(chDbcHandle, (int)通道号);
}
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;
DBCParser.MsgDatabase[(int)通道号].Clear();
}
}
}
#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,自定义报文也能收到)
var rawData = (ZLGCAN.ZCAN_Receive_Data)Marshal.PtrToStructure(framePtr, typeof(ZLGCAN.ZCAN_Receive_Data))!;
OnRawMessageReceived?.Invoke(channelIndex, rawData.frame.can_id, rawData.frame.data, rawData.timestamp, false, rawData.frame.can_dlc);
// 只锁定当前通道的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);
// 触发原始报文事件(不依赖 DBC,自定义报文也能收到)
var rawFdData = (ZLGCAN.ZCAN_ReceiveFD_Data)Marshal.PtrToStructure(framePtr, typeof(ZLGCAN.ZCAN_ReceiveFD_Data))!;
OnRawMessageReceived?.Invoke(channelIndex, rawFdData.frame.can_id, rawFdData.frame.data, rawFdData.timestamp, true, rawFdData.frame.len);
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 = CANFD0 = CAN</param>
/// <returns>是否成功启动/发送</returns>
[Browsable(false)]
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();
var sendTask = Task.Run(async () =>
{
while (!cts.Token.IsCancellationRequested)
{
if (_isClosing) break; // 关闭流程中立即退出
try
{
lock (_channelLocks[通道号])
{
if (_channelHandles[通道号] == IntPtr.Zero || _isClosing) break;
发送DBC定义报文单次(通道号, ID, 信号物理值字典, 是否使用CANFD);
}
await Task.Delay(循环间隔毫秒, cts.Token);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
Logger.LoggerHelper.Error($"[ZLGCANFD] 循环发送 DBC 报文失败: {ex.Message}");
break;
}
}
// 清理:仅当字典中存的仍然是本任务创建的 CTS 时才移除,避免误删新任务的 CTS
if (_cyclicSenders.TryGetValue((通道号, ID), out var current) && ReferenceEquals(current.Cts, cts))
{
_cyclicSenders.TryRemove((通道号, ID), out _);
}
cts.Dispose();
}, cts.Token);
_cyclicSenders[(通道号, ID)] = (cts, sendTask);
return true;
}
/// <summary>
/// 停止指定通道、指定帧 ID 的循环发送。
/// </summary>
public virtual void 停止循环发送(uint 通道号, uint ID)
{
if (_cyclicSenders.TryRemove((通道号, ID), out var entry))
{
entry.Cts.Cancel();
// 不在这里 Dispose,由 Task 的清理代码负责释放,避免 Task 仍在使用已释放的 Token
}
}
/// <summary>
/// 停止所有 DBC 循环发送任务。
/// </summary>
public virtual void 停止所有循环发送()
{
var entries = _cyclicSenders.ToArray();
// 1. 取消所有 CTS(不 Dispose,由 Task 清理代码负责)
foreach (var kvp in entries)
{
kvp.Value.Cts.Cancel();
}
// 2. 等待所有循环发送 Task 退出(最多 3 秒)
var tasks = new List<Task>();
foreach (var kvp in entries)
{
if (!kvp.Value.SendTask.IsCompleted)
tasks.Add(kvp.Value.SendTask);
}
if (tasks.Count > 0)
{
try
{
Task.WaitAll(tasks.ToArray(), TimeSpan.FromSeconds(3));
}
catch { /* 忽略等待异常 */ }
}
_cyclicSenders.Clear();
}
/// <summary>
/// 清除指定通道、指定帧 ID 的信号覆盖值,恢复为 DBC 初始值。
/// </summary>
public virtual void 清除信号覆盖(uint 通道号, uint ID)
{
if (_signalOverrides.TryRemove((通道号, ID), out var dict))
{
lock (dict) dict.Clear();
}
}
/// <summary>
/// 清除指定通道所有帧的信号覆盖值。
/// </summary>
public virtual void 清除通道信号覆盖(uint 通道号)
{
foreach (var key in _signalOverrides.Keys)
{
if (key.通道号 == 通道号 && _signalOverrides.TryRemove(key, out var dict))
{
lock (dict) dict.Clear();
}
}
}
/// <summary>
/// 单次发送 DBC 报文(调用方已持有通道锁)。
/// </summary>
[Browsable(false)]
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 = 2 };
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 = 2 };
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>
/// 设置并发送指定信号值(帧 ID 支持 string 兼容模式)。
/// 其余未指定信号使用 DBC 中定义的初始值填充,避免总线数据被意外清零。
/// </summary>
/// <param name="通道号">CAN/CANFD 通道索引</param>
/// <param name="帧IDStr">对应报文的帧 ID(可以是 "26"、"0x1A"、"1A"</param>
/// <param name="信号名称">DBC 中定义的英文信号名称(不区分大小写)</param>
/// <param name="物理值">要写入的实际物理数值</param>
/// <param name="循环发送间隔毫秒">0 = 单次发送;>0 = 周期循环发送(单位毫秒)</param>
/// <returns>操作是否成功</returns>
public virtual bool 设置报文(uint 通道号, string IDStr, string 信号名称, double 物理值, int 循环发送间隔毫秒 = 0, bool 直接发送 = false)
{
if (!TryParseFrameId(IDStr, out uint ID))
{
Console.WriteLine($"[ZLGCANFD] 设置报文失败: 无法解析的帧 ID '{帧IDStr}'");
return false;
}
if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
if (!_isDbcLoadedArray[通道号]) return false;
if (string.IsNullOrWhiteSpace(信号名称)) return false;
// 将本次设置的信号值写入持久化覆盖表(累积,不会丢失之前设置的值)
var overrides = _signalOverrides.GetOrAdd((通道号, ID), _ => new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase));
lock (overrides)
{
overrides[信号名称] = 物理值;
}
// 构建发送字典:DBC 初始值 + 持久化覆盖表(已包含本次设置)
var merged = 构建报文发送字典(通道号, ID);
if (直接发送) return 发送DBC定义报文(通道号, ID, merged, 循环发送间隔毫秒);
else return true;
}
/// <summary>
/// 发送报文:以 DBC 中所有信号的初始值作为默认值发送(帧 ID 支持 string 兼容模式)。
/// 如果之前调用 设置报文 保存过覆盖值,则会优先使用覆盖值。
/// </summary>
/// <param name="通道号">CAN/CANFD 通道索引</param>
/// <param name="帧IDStr">DBC 中定义的帧 ID(可以是 "26"、"0x1A"、"1A"</param>
/// <param name="循环发送间隔毫秒">0 = 单次发送;>0 = 周期循环发送(毫秒)</param>
/// <param name="是否使用CANFD">1 = 以 CANFD 格式发送;0 = 经典 CAN 格式</param>
public virtual bool 发送报文(uint 通道号, string IDStr, int 循环发送间隔毫秒 = 0, int 是否使用CANFD = 1)
{
if (!TryParseFrameId(IDStr, out uint ID))
{
Console.WriteLine($"[ZLGCANFD] 发送报文失败: 无法解析的帧 ID '{帧IDStr}'");
return false;
}
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>
/// 发送自定义报文(原始字节,帧 ID 支持 string 兼容模式)。
/// </summary>
/// <param name="通道号">CAN/CANFD 通道索引</param>
/// <param name="帧IDStr">帧 ID(可以是 "26"、"0x1A"、"1A");扩展帧会自动置位 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 = CANFDfalse = 经典 CAN</param>
/// <param name="开启波特率加速BRS">CANFD 是否开启波特率加速</param>
public virtual bool 发送自定义报文(
uint 通道号,
string IDStr,
byte[] 原始数据,
byte dlcLength,
bool 是否是扩展帧 = false,
bool 是否是CANFD = true,
bool 开启波特率加速BRS = true)
{
if (!TryParseFrameId(IDStr, out uint ID))
{
Console.WriteLine($"[ZLGCANFD] 发送自定义报文失败: 无法解析的帧 ID '{帧IDStr}'");
return false;
}
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 = 2 };
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 = 2 };
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>
/// 直接发送 8 字节原始报文(经典 CAN 或 CANFD 均可)。
/// 无需加载 DBC,适合手动构造简单报文。
/// </summary>
/// <param name="通道号">通道号(0 ~ MaxChannels-1</param>
/// <param name="帧ID">帧 ID(标准 11 位,扩展帧自动置位 0x80000000</param>
/// <param name="data">原始数据;超过 8 字节会自动截断,不足 8 字节自动补零</param>
/// <param name="是否是扩展帧">是否为 29 位扩展帧</param>
/// <param name="是否是CANFD">true = CANFD 格式;false = 经典 CAN 格式</param>
/// <param name="开启波特率加速BRS">CANFD 是否开启波特率加速(经典 CAN 忽略此参数)</param>
/// <returns>是否发送成功</returns>
public virtual bool 发送原始报文(
uint 通道号,
uint ID,
byte[] data,
bool 是否是扩展帧 = false,
bool 是否是CANFD = true,
bool 开启波特率加速BRS = true)
{
if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
byte[] buffer = new byte[8];
if (data != null)
{
Array.Copy(data, 0, buffer, 0, Math.Min(data.Length, 8));
}
uint canId = ID & 0x7FFFFFFF;
if (是否是扩展帧) canId |= 0x80000000;
lock (_channelLocks[通道号])
{
if (是否是CANFD)
{
ZLGCAN.canfd_frame fdFrame = new ZLGCAN.canfd_frame
{
can_id = canId,
len = 8,
flags = (byte)((是否是扩展帧 ? 0x01 : 0x00) | (开启波特率加速BRS ? 0x02 : 0x00) | 0x20),
data = new byte[64]
};
Array.Copy(buffer, 0, fdFrame.data, 0, 8);
ZLGCAN.ZCAN_TransmitFD_Data txFdData = new ZLGCAN.ZCAN_TransmitFD_Data { frame = fdFrame, transmit_type = 2 };
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 = 8,
__pad = 0x20,
data = new byte[8]
};
Array.Copy(buffer, 0, standardFrame.data, 0, 8);
ZLGCAN.ZCAN_Transmit_Data txData = new ZLGCAN.ZCAN_Transmit_Data { frame = standardFrame, transmit_type = 2 };
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);
// 1. DBC 初始值打底
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)
{
dict[signalName] = signal.initialValue;
}
}
// 2. 叠加持久化覆盖表(之前 设置报文 累积的值)
if (_signalOverrides.TryGetValue((通道号, ID), out var persisted))
{
lock (persisted)
{
foreach (var kvp in persisted)
dict[kvp.Key] = kvp.Value;
}
}
// 3. 叠加本次调用传入的额外覆盖(优先级最高)
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>
/// <summary>
/// 智能解析帧 ID 字符串,兼容 10 进制、16 进制(如 "0x1A", "1A", "26"
/// </summary>
/// <param name="idStr">输入的帧 ID 字符串</param>
/// <param name="frameId">解析成功后的 uint 帧 ID</param>
/// <returns>是否解析成功</returns>
[Browsable(false)]
public static bool TryParseFrameId(string idStr, out uint frameId)
{
frameId = 0;
if (string.IsNullOrWhiteSpace(idStr)) return false;
idStr = idStr.Trim();
try
{
// 1. 处理带 0x 或 0X 的标准 16 进制
if (idStr.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
frameId = Convert.ToUInt32(idStr.Substring(2), 16);
return true;
}
// 2. 尝试当做纯 10 进制解析(如 "26"
if (uint.TryParse(idStr, out frameId))
{
return true;
}
// 3. 如果不是纯数字,但符合 16 进制特征(如 "1A"、"ABC"),尝试按 16 进制解析
// 使用 System.Globalization.NumberStyles.HexNumber
if (uint.TryParse(idStr, System.Globalization.NumberStyles.HexNumber, null, out frameId))
{
return true;
}
}
catch
{
return false;
}
return false;
}
[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卡设备();
}
}
}