Files
ADP/ZLGUSBCANFD/USBCANFD.cs
T

1184 lines
53 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
{
/// <summary>
/// 周立功 USBCANFD 系列 CAN 卡控制类(如 USBCANFD-400U),封装设备连接、通道初始化、
/// DBC 矩阵加载与解码、报文发送(单次/循环)、信号物理值设置等功能。
/// 每个通道拥有独立的 DBC 引擎句柄与细粒度锁,支持多台架并行互不干扰。
/// </summary>
[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>>();
// 最新解码报文缓存:Key = (通道号, 帧ID)Value = 最近一次 DBC 解码成功的报文快照(含各信号原始值)
// 接收线程解码成功时写入,供 获取报文信号值 主动查询最新信号物理值
private readonly ConcurrentDictionary<(uint 通道号, uint ID), ZDBC.DBCMessage> _latestDecodedMessages = new ConcurrentDictionary<(uint, uint), ZDBC.DBCMessage>();
// 动态硬件参数
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;
/// <summary>
/// 构造函数,配置设备类型、索引、通道数及所有通道共用的波特率与终端电阻参数,
/// 并初始化通道状态数组与 DBC 解析器。
/// </summary>
/// <param name="deviceType">设备类型,76 为 USBCANFD-400U</param>
/// <param name="deviceIndex">设备索引,多台同型号设备时区分,默认 0</param>
/// <param name="maxChannels">通道数量,默认 4</param>
/// <param name="abitBaud">仲裁域波特率,默认 "500000"</param>
/// <param name="dbitBaud">数据域波特率(仅 CANFD 生效),默认 "2000000"</param>
/// <param name="enableTerminalResistance">是否启用通道内部终端电阻,默认 true</param>
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>
/// <returns>是否打开成功;设备已打开时重复调用直接返回 true</returns>
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>
/// <param name="通道号">通道索引 (0 起始)</param>
/// <returns>是否初始化并启动成功;通道已启动时重复调用直接返回 true</returns>
/// <exception cref="InvalidOperationException">未先调用“打开设备()”时抛出</exception>
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;
}
}
/// <summary>
/// 关闭 CAN 卡设备:先停止所有循环发送与接收轮询线程,再复位全部通道、
/// 释放各通道 DBC 资源,最后关闭设备主句柄。
/// </summary>
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>
/// <param name="通道号">通道索引 (0 起始)</param>
/// <param name="dbcFilePath">DBC 矩阵文件的完整路径</param>
/// <returns>是否加载成功;该通道已加载过则直接返回 true</returns>
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;
}
}
}
/// <summary>
/// 释放指定通道已加载的 DBC 资源,并清空该通道的报文数据库。
/// </summary>
/// <param name="通道号">通道索引 (0 起始)</param>
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();
// 同步清理该通道的最新解码报文缓存,避免残留旧信号值
foreach (var key in _latestDecodedMessages.Keys)
{
if (key.通道号 == 通道号)
_latestDecodedMessages.TryRemove(key, out _);
}
}
}
}
#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));
// 缓存最新解码报文快照,供 获取报文信号值 查询
_latestDecodedMessages[(channelIndex, msg.nID)] = msg;
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));
// 缓存最新解码报文快照,供 获取报文信号值 查询
_latestDecodedMessages[(channelIndex, msg.nID)] = msg;
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>
/// <param name="通道号">通道索引 (0 起始)</param>
/// <param name="帧ID">要停止循环发送的报文帧 ID</param>
public virtual void 停止循环发送(uint 通道号, uint ID)
{
if (_cyclicSenders.TryRemove((通道号, ID), out var entry))
{
entry.Cts.Cancel();
// 不在这里 Dispose,由 Task 的清理代码负责释放,避免 Task 仍在使用已释放的 Token
}
}
/// <summary>
/// 停止所有 DBC 循环发送任务,并等待全部发送任务退出(最多 3 秒)。
/// </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>
/// <param name="通道号">通道索引 (0 起始)</param>
/// <param name="帧ID">要清除覆盖值的报文帧 ID</param>
public virtual void 清除信号覆盖(uint 通道号, uint ID)
{
if (_signalOverrides.TryRemove((通道号, ID), out var dict))
{
lock (dict) dict.Clear();
}
}
/// <summary>
/// 清除指定通道所有帧的信号覆盖值,全部恢复为 DBC 初始值。
/// </summary>
/// <param name="通道号">通道索引 (0 起始)</param>
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>
/// <param name="直接发送">true = 设置后立即发送;false = 仅记录到持久化覆盖表,等后续 发送报文 时生效</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>
/// <returns>是否成功启动/发送</returns>
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 兼容模式)。
/// 循环发送间隔毫秒 = 0 时只发送一次;>0 时按指定间隔循环发送,可调用 停止循环发送 停止。
/// </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>
/// <param name="循环发送间隔毫秒">0 = 单次发送;>0 = 周期循环发送(毫秒)</param>
/// <returns>是否成功启动/发送</returns>
public virtual bool 发送自定义报文(
uint 通道号,
string IDStr,
byte[] 原始数据,
byte dlcLength,
bool 是否是扩展帧 = false,
bool 是否是CANFD = true,
bool 开启波特率加速BRS = true,
int 循环发送间隔毫秒 = 0)
{
if (!TryParseFrameId(IDStr, out uint ID))
{
Console.WriteLine($"[ZLGCANFD] 发送自定义报文失败: 无法解析的帧 ID '{帧IDStr}'");
return false;
}
if (通道号 >= _maxChannels || _channelHandles[通道号] == IntPtr.Zero) return false;
if (原始数据 == null) return false;
if (循环发送间隔毫秒 < 0) throw new ArgumentOutOfRangeException(nameof(循环发送间隔毫秒), "循环发送间隔必须大于或等于 0。");
// 扩展帧需要把最高位标志位置起来
uint canId = ID & 0x7FFFFFFF;
if (是否是扩展帧) canId |= 0x80000000;
// 单次发送
if (循环发送间隔毫秒 == 0)
{
return 发送自定义报文单次(通道号, canId, 原始数据, dlcLength, 是否是扩展帧, 是否是CANFD, 开启波特率加速BRS);
}
// 循环发送:先停止同通道同帧 ID 的旧循环,再启动新循环
停止循环发送(通道号, ID);
// 拷贝原始数据,避免外部修改影响循环发送
byte[] dataCopy = new byte[原始数据.Length];
Array.Copy(原始数据, dataCopy, 原始数据.Length);
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;
发送自定义报文单次(通道号, canId, dataCopy, dlcLength, 是否是扩展帧, 是否是CANFD, 开启波特率加速BRS);
}
await Task.Delay(循环发送间隔毫秒, cts.Token);
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
Logger.LoggerHelper.Error($"[ZLGCANFD] 循环发送自定义报文失败: {ex.Message}");
break;
}
}
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>
/// 发送自定义报文的单次执行(内部方法,无循环逻辑)。
/// </summary>
[Browsable(false)]
private bool 发送自定义报文单次(
uint 通道号,
uint canId,
byte[] 原始数据,
byte dlcLength,
bool 是否是扩展帧,
bool 是否是CANFD,
bool 开启波特率加速BRS)
{
lock (_channelLocks[通道号])
{
if (是否是CANFD)
{
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),
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 4.2 报文信号查询
/// <summary>
/// 获取指定通道最新一帧 DBC 报文中某个信号的物理值(帧 ID 支持 string 兼容模式)。
/// <para>
/// 返回值为最近一次接收并解码成功的报文快照:物理值 = 原始值 × 因子 + 偏移,
/// 与监控界面广播的数值口径一致。若报文尚未收到、DBC 未加载、报文或信号名称不存在,
/// 将记录错误日志并返回 0。
/// </para>
/// </summary>
/// <param name="通道号">CAN/CANFD 通道索引</param>
/// <param name="帧IDStr">对应报文的帧 ID(可以是 "26"、"0x1A"、"1A"</param>
/// <param name="信号名称">DBC 中定义的英文信号名称(不区分大小写)</param>
/// <returns>信号物理值;失败时返回 0</returns>
public virtual double 获取报文信号值(uint 通道号, string IDStr, string 信号名称)
{
if (!TryParseFrameId(IDStr, out uint ID))
{
LoggerHelper.Error($"[ZLGCANFD] 获取报文信号值失败: 无法解析的帧 ID '{帧IDStr}'");
return 0;
}
if (通道号 >= _maxChannels)
{
LoggerHelper.Error($"[ZLGCANFD] 获取报文信号值失败: 通道号 {通道号} 越界(有效范围 0~{_maxChannels - 1}");
return 0;
}
if (!_isDbcLoadedArray[通道号])
{
LoggerHelper.Error($"[ZLGCANFD] 获取报文信号值失败: 通道 {通道号} 尚未加载 DBC 文件");
return 0;
}
if (string.IsNullOrWhiteSpace(信号名称))
{
LoggerHelper.Error("[ZLGCANFD] 获取报文信号值失败: 信号名称为空");
return 0;
}
// 查找该通道该帧 ID 最近一次解码成功的报文快照
if (!_latestDecodedMessages.TryGetValue((通道号, ID), out var msg))
{
LoggerHelper.Error($"[ZLGCANFD] 获取报文信号值失败: 通道 {通道号} 未收到帧 ID 0x{帧ID:X} 的报文(尚未接收或 DBC 中无此报文)");
return 0;
}
// 在报文信号集合中查找目标信号(不区分大小写,与 设置报文 的信号匹配口径一致)
for (int i = 0; i < msg.nSignalCount; i++)
{
var signal = msg.vSignals[i];
string name = Encoding.Default.GetString(signal.strName).TrimEnd('\0');
if (string.Equals(name, 信号名称, StringComparison.OrdinalIgnoreCase))
{
// 物理值 = 原始值 × 因子 + 偏移(与 CANSignalBroadcaster 广播公式一致)
return signal.nRawvalue * signal.nFactor + signal.nOffset;
}
}
LoggerHelper.Error($"[ZLGCANFD] 获取报文信号值失败: 帧 ID 0x{帧ID:X} 的报文中不存在信号 '{信号名称}'");
return 0;
}
#endregion
#region 5. 辅助工具子系统
/// <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卡设备();
}
}
}