Files
ADP/ZLGUSBCANFD/USBCANFD.cs
2026-07-28 14:41:46 +08:00

983 lines
42 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;
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句柄进行解码高并发完全不卡顿
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 = CANFD0 = 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();
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>
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卡设备();
}
}
}