This commit is contained in:
2026-03-02 11:20:08 +08:00
parent 74338fdb3a
commit 5be18ded2e
21 changed files with 5984 additions and 224 deletions

View File

@@ -13,15 +13,38 @@ using System.Text;
namespace CapMachine.Wpf.CanDrive
{
/// <summary>
/// Toomoss CAN
/// 图莫斯(ToomossCAN 驱动封装。
/// </summary>
/// <remarks>
/// 职责边界:
/// - 负责图莫斯 USB2XXX SDK 的设备扫描/打开/初始化/关闭;
/// - 负责 DBC 文件解析,并提供“信号值 <-> CAN 帧”的互转(通过 SDK 的 DBCParser 接口);
/// - 提供三类发送能力:
/// - 单次发送(<see cref="SendCanMsg"/>
/// - 软件侧精确周期发送(<see cref="StartPrecisionCycleSendMsg"/>
/// - 硬件侧调度表发送(<see cref="StartSchedule"/> / <see cref="StopSchedule"/>,以及 <see cref="UpdateSchDataByCmdDataChanged"/> 增量更新);
/// - 提供接收能力:后台轮询读取报文并同步到 DBC 信号值(<see cref="StartCycleReviceCanMsg"/>)。
///
/// 线程与资源:
/// - 发送/接收/调度表更新均可能在后台线程运行;
/// - 本类会分配非托管内存IntPtr 缓冲区)。必须通过 <see cref="CloseDevice"/> 释放,且释放过程需要与接收线程互斥。
///
/// 重要约束:
/// - <see cref="DBCHandle"/> 为 0 表示 DBC 未解析成功,任何 DBC_* 调用都应视为不可用;
/// - <see cref="WriteCANIndex"/> / <see cref="ReadCANIndex"/> 表示 CAN 通道索引0=CAN1,1=CAN2当前实现约定读写通道一致。
/// </remarks>
public class ToomossCan : BindableBase
{
/// <summary>
/// Prism 容器,用于解析运行时依赖(如 <see cref="HighSpeedDataService"/>)。
/// </summary>
private readonly IContainerProvider ContainerProvider;
/// <summary>
/// 实例化函数
/// 构造函数
/// </summary>
/// <param name="containerProvider">DI 容器。</param>
/// <param name="logService">日志服务(外部注入,避免驱动类自己创建日志对象)。</param>
public ToomossCan(IContainerProvider containerProvider, ILogService logService)
{
ContainerProvider = containerProvider;
@@ -35,8 +58,19 @@ namespace CapMachine.Wpf.CanDrive
}
/// <summary>
/// 开始CAN驱动
/// 启动 CAN 驱动(设备准备流程)。
/// </summary>
/// <remarks>
/// 执行顺序与 SDK 依赖一致:
/// 1) 校验 DLL 是否存在;
/// 2) 扫描设备并选取句柄;
/// 3) 打开设备;
/// 4) 读取设备信息;
/// 5) 获取 CAN 初始化配置;
/// 6) 初始化 CAN。
///
/// 注意:该方法不解析 DBCDBC 解析由 <see cref="StartDbc"/> 显式触发。
/// </remarks>
public void StartCanDrive()
{
IsExistsDllFile();
@@ -50,18 +84,20 @@ namespace CapMachine.Wpf.CanDrive
}
/// <summary>
/// HighSpeedDataService 实例
/// 高速数据服务:用于把接收到的原始报文写入高速记录/显示通道。
/// </summary>
public HighSpeedDataService HighSpeedDataService { get; set; }
/// <summary>
/// Logger 实例
/// 日志服务。
/// </summary>
public ILogService LoggerService { get; set; }
/// <summary>
/// 开始Dbc文件写入
/// 解析并加载 DBC 文件。
/// </summary>
/// <param name="DbcPath">DBC 文件路径。</param>
/// <returns>解析得到的信号列表(用于 UI 展示与实时值刷新)。</returns>
public ObservableCollection<CanDbcModel> StartDbc(string DbcPath)
{
DBC_Parser(DbcPath);
@@ -77,38 +113,47 @@ namespace CapMachine.Wpf.CanDrive
//}
/// <summary>
/// Dbc消息集合
/// 包括读取的实时值和数据
/// DBC 解析得到的信号集合
/// </summary>
/// <remarks>
/// - 该集合的每个元素代表一个信号Signal包含所属报文名、信号名、单位/描述、实时值等。
/// - 接收线程会持续更新 <see cref="CanDbcModel.SignalRtValue"/>。
/// </remarks>
public ObservableCollection<CanDbcModel> ListCanDbcModel { get; set; } = new ObservableCollection<CanDbcModel>();
#region
/// <summary>
/// 设备固件信息
/// 设备固件信息(由 SDK 填充)。
/// </summary>
public USB_DEVICE.DEVICE_INFO DevInfo = new USB_DEVICE.DEVICE_INFO();
/// <summary>
/// CAN Config
/// CAN 初始化配置(由 <see cref="GetCANConfig"/> 读取或计算后用于 <see cref="InitCAN"/>)。
/// </summary>
USB2CAN.CAN_INIT_CONFIG CANConfig = new USB2CAN.CAN_INIT_CONFIG();
/// <summary>
/// DBC加载后获取的Handle
/// DBC Handle
/// DBC 解析成功后得到的句柄。
/// </summary>
/// <remarks>
/// - 为 0 表示 DBC 未解析/解析失败;
/// - 发送与接收解析均依赖该句柄。
/// </remarks>
public UInt64 DBCHandle { get; set; }
/// <summary>
/// 扫描设备Handle集合
/// 扫描到的设备句柄数组SDK 输出)。
/// </summary>
public Int32[] DevHandles { get; set; } = new Int32[20];
/// <summary>
/// 扫描设备Handle
/// 当前打开的设备句柄。
/// </summary>
/// <remarks>
/// 该值由 <see cref="ScanDevice"/> 选取并在 <see cref="OpenDevice"/> 中打开。
/// </remarks>
public Int32 DevHandle { get; set; } = 0;
/// <summary>
@@ -149,7 +194,7 @@ namespace CapMachine.Wpf.CanDrive
private bool _OpenState;
/// <summary>
/// 打开设备的状态
/// 设备打开状态
/// </summary>
public bool OpenState
{
@@ -159,7 +204,7 @@ namespace CapMachine.Wpf.CanDrive
private bool _DbcParserState;
/// <summary>
/// DBC解析状态
/// DBC 解析状态
/// </summary>
public bool DbcParserState
{
@@ -174,6 +219,12 @@ namespace CapMachine.Wpf.CanDrive
public Int32 ret { get; set; }
/// <summary>
/// USB2XXX SDK 的 DLL 文件名。
/// </summary>
/// <remarks>
/// 注意:此处仅校验 DLL 是否存在,实际的 DllImport 绑定仍由运行时加载机制决定。
/// </remarks>
public string dllFilePath { get; set; } = "USB2XXX.dll";
@@ -185,10 +236,12 @@ namespace CapMachine.Wpf.CanDrive
#endregion
/// <summary>
/// ******************【1】*********************
/// 是否存在Dll文件
/// 【1】检查 USB2XXX SDK 的 DLL 是否存在。
/// </summary>
/// <returns></returns>
/// <returns>存在返回 true否则返回 false。</returns>
/// <remarks>
/// 这是最前置的防御性校验,避免后续调用直接触发 DllNotFoundException。
/// </remarks>
public bool IsExistsDllFile()
{
if (!File.Exists(dllFilePath))
@@ -202,10 +255,13 @@ namespace CapMachine.Wpf.CanDrive
}
/// <summary>
/// ******************【2】*********************
/// 扫描查找设备,并将每个设备的唯一设备号存放到数组中,后面的函数需要用到
/// 【2】扫描设备。
/// </summary>
/// <returns></returns>
/// <returns>扫描到设备返回 true否则返回 false。</returns>
/// <remarks>
/// SDK 会把设备句柄写入 <see cref="DevHandles"/>,并返回设备数量。
/// 当前实现默认选取第一个设备句柄作为 <see cref="DevHandle"/>。
/// </remarks>
public bool ScanDevice()
{
DevNum = USB_DEVICE.USB_ScanDevice(DevHandles);
@@ -330,9 +386,14 @@ namespace CapMachine.Wpf.CanDrive
}
/// <summary>
/// ******************【7】*********************
/// DBC解析
/// 【7】解析 DBC 文件并构建信号列表。
/// </summary>
/// <param name="Path">DBC 文件路径。</param>
/// <remarks>
/// 解析结果:
/// - <see cref="DBCHandle"/>:后续信号赋值/取值/同步帧均依赖它;
/// - <see cref="ListCanDbcModel"/>:用于 UI 列表展示与实时值更新。
/// </remarks>
public void DBC_Parser(string Path)
{
//解析DBC文件
@@ -390,9 +451,18 @@ namespace CapMachine.Wpf.CanDrive
}
/// <summary>
/// 发送CAN数据
/// 发送一次
/// 单次发送:根据 CmdData 中的信号值构建 CAN 报文并发送。
/// </summary>
/// <param name="CmdData">待发送的信号指令集合(按 MsgName 分组后组成帧)。</param>
/// <remarks>
/// 数据流:
/// 1) 按 MsgName 分组(每组代表同一个报文/帧);
/// 2) 对每个分组:逐信号调用 DBC_SetSignalValue 写入 DBC 缓存;
/// 3) 调用 DBC_SyncValueToCANMsg 生成帧;
/// 4) 调用 CAN_SendMsg 一次性批量发送。
///
/// 注意:此方法会分配临时非托管内存 msgPt并在结束前释放。
/// </remarks>
public void SendCanMsg(List<CanCmdData> CmdData)
{
var GroupMsg = CmdData.GroupBy(x => x.MsgName);
@@ -403,6 +473,7 @@ namespace CapMachine.Wpf.CanDrive
CanMsg[i].Data = new Byte[64];
}
// 非托管缓冲SDK API 以指针形式写入帧结构体
IntPtr msgPt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)));
int Index = 0;
//循环给MSG赋值数据
@@ -425,7 +496,7 @@ namespace CapMachine.Wpf.CanDrive
//通过DBC写入数据后生成CanMsg
//将信号值填入CAN消息里面
//释放申请的临时缓冲区
// 释放临时非托管缓冲区,避免内存泄漏
Marshal.FreeHGlobal(msgPt);
Console.WriteLine("");
//发送CAN数据
@@ -443,8 +514,11 @@ namespace CapMachine.Wpf.CanDrive
private bool _IsCycleRevice;
/// <summary>
/// 是否循环接收数据
/// 是否循环接收数据
/// </summary>
/// <remarks>
/// 由 UI/Service 控制开关,接收任务 <see cref="StartCycleReviceCanMsg"/> 以该标记作为退出条件。
/// </remarks>
public bool IsCycleRevice
{
get { return _IsCycleRevice; }
@@ -454,8 +528,13 @@ namespace CapMachine.Wpf.CanDrive
private bool _IsCycleSend;
/// <summary>
/// 是否循环发送数据
/// 是否循环发送数据
/// </summary>
/// <remarks>
/// 由 UI/Service 控制开关:
/// - 软件精确周期发送:<see cref="StartPrecisionCycleSendMsg"/> / <see cref="StopCycleSendMsg"/>
/// - 调度表发送:<see cref="StartSchedule"/> / <see cref="StopSchedule"/>。
/// </remarks>
public bool IsCycleSend
{
get { return _IsCycleSend; }
@@ -464,28 +543,41 @@ namespace CapMachine.Wpf.CanDrive
/// <summary>
/// 循环发送数据
/// 循环发送周期(毫秒)。
/// </summary>
/// <remarks>
/// 该周期用于软件侧循环发送/接收 Task.Delay硬件调度表发送周期由调度表项的 Cycle 决定。
/// </remarks>
public ushort SendCycle { get; set; } = 100;
/// <summary>
/// 循环接受数据
/// 循环接收轮询周期(毫秒)。
/// </summary>
public ushort ReviceCycle { get; set; } = 500;
/// <summary>
/// CycleRevice 扫描Task
/// 循环接收任务。
/// </summary>
/// <remarks>
/// 这里是 static意味着同进程内多个实例会共享该任务引用这在多设备场景需要特别注意
/// </remarks>
private static Task CycleReviceTask { get; set; }
/// <summary>
/// CycleSend 扫描Task
/// 循环发送任务。
/// </summary>
/// <remarks>
/// 这里是 static意味着同进程内多个实例会共享该任务引用这在多设备场景需要特别注意
/// </remarks>
private static Task CycleSendTask { get; set; }
/// <summary>
/// 定时扫描更新数据 扫描Task
/// (旧方案)周期性刷新调度表任务。
/// </summary>
/// <remarks>
/// 当前已引入“CmdData 变化事件 -> 增量更新调度表”的机制(见 <see cref="UpdateSchDataByCmdDataChanged"/>
/// 该 Task 属于保留兼容路径。
/// </remarks>
private static Task CycleUpdateCmdTask { get; set; }
StringBuilder ValueSb = new StringBuilder(16);
@@ -519,8 +611,12 @@ namespace CapMachine.Wpf.CanDrive
private bool _IsSendOk;
/// <summary>
/// 发送报文是否OK
/// 最近一次发送是否成功。
/// </summary>
/// <remarks>
/// - 软件循环发送路径:由 CAN_SendMsg 返回值决定;
/// - 调度表更新路径:由 CAN_UpdateSchedule 返回值决定。
/// </remarks>
public bool IsSendOk
{
get { return _IsSendOk; }
@@ -537,8 +633,14 @@ namespace CapMachine.Wpf.CanDrive
private bool _IsReviceOk;
/// <summary>
/// 接收报文是否OK
/// 最近一次接收是否成功。
/// </summary>
/// <remarks>
/// 该状态反映最近一次轮询读取 CAN_GetMsgWithSize 的结果:
/// - CanNum &gt; 0成功收到数据
/// - CanNum == 0本轮无数据
/// - CanNum &lt; 0调用失败。
/// </remarks>
public bool IsReviceOk
{
get { return _IsReviceOk; }
@@ -553,13 +655,20 @@ namespace CapMachine.Wpf.CanDrive
}
/// <summary>
/// 要发送的数据
/// 当前激活的指令集合(由 Service/UI 下发)。
/// </summary>
/// <remarks>
/// - 每个元素代表一个信号的写入指令;
/// - 发送时按 MsgName 分组,同一 MsgName 下的多个信号会被打包到同一帧。
/// </remarks>
public List<CanCmdData> CmdData { get; set; } = new List<CanCmdData>();
/// <summary>
/// 循环发送数据
/// (普通)软件循环发送:以 <see cref="SendCycle"/> 为周期构建帧并发送。
/// </summary>
/// <remarks>
/// 该方法属于早期实现,精度与线程抖动相关;若追求更高精度建议使用 <see cref="StartPrecisionCycleSendMsg"/>。
/// </remarks>
public void StartCycleSendMsg()
{
CycleSendTask = Task.Run(async () =>
@@ -865,8 +974,16 @@ namespace CapMachine.Wpf.CanDrive
private static readonly Random _random = new Random();
/// <summary>
/// 精确周期发送CAN数据
/// 软件侧精确周期发送 CAN 数据
/// </summary>
/// <remarks>
/// 关键点:
/// - 使用 <see cref="Stopwatch"/> tick 作为时间基准,减少 DateTime 带来的抖动;
/// - 使用 Task.Delay + SpinWait 组合:大等待让出 CPU小等待自旋微调
/// - 使用 <see cref="CycleSendCts"/> 支持外部取消。
///
/// 注意:调用方需要先将 <see cref="IsCycleSend"/> 置为 true停止时请调用 <see cref="StopCycleSendMsg"/>。
/// </remarks>
public void StartPrecisionCycleSendMsg()
{
// 创建取消标记源 用于控制任务的取消 允许在需要时通过取消令牌来优雅停止任务
@@ -953,6 +1070,7 @@ namespace CapMachine.Wpf.CanDrive
CanMsg[i].Data = new Byte[64];
}
// 发送构帧临时缓冲:每轮申请/释放,避免与其他线程共享同一指针导致并发问题
IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)));
int Index = 0;
//循环给MSG赋值数据
@@ -970,7 +1088,7 @@ namespace CapMachine.Wpf.CanDrive
//通过DBC写入数据后生成CanMsg
//将信号值填入CAN消息里面
//释放申请的临时缓冲区
// 释放非托管缓冲区(务必释放,否则长时间运行会造成内存泄漏)
Marshal.FreeHGlobal(msgPtSend);
//发送CAN数据
@@ -1032,8 +1150,12 @@ namespace CapMachine.Wpf.CanDrive
/// <summary>
/// 修改停止发送的方法
/// 停止软件循环发送。
/// </summary>
/// <remarks>
/// - 置 <see cref="IsCycleSend"/> 为 false循环自然退出
/// - 触发 <see cref="CycleSendCts"/> 取消,用于尽快唤醒 Delay 并退出。
/// </remarks>
public void StopCycleSendMsg()
{
IsCycleSend = false;
@@ -1046,8 +1168,11 @@ namespace CapMachine.Wpf.CanDrive
private bool _SchEnable;
/// <summary>
/// 调度表使能
/// 调度表使能开关。
/// </summary>
/// <remarks>
/// 该开关用于控制“硬件调度表发送”及“CmdData 变化时是否增量更新调度表”。
/// </remarks>
public bool SchEnable
{
get { return _SchEnable; }
@@ -1060,13 +1185,21 @@ namespace CapMachine.Wpf.CanDrive
/// <summary>
/// 调度表的发送的报文数据
/// 当前已下发到设备调度表的帧缓存。
/// </summary>
/// <remarks>
/// - 在 <see cref="StartSchedule"/> 中构建,并通过 CAN_SetSchedule 下发;
/// - 在 <see cref="UpdateSchDataByCmdDataChanged"/> 中重建并通过 CAN_UpdateSchedule 覆盖更新。
/// </remarks>
private USB2CAN.CAN_MSG[] SchCanMsg { get; set; }
/// <summary>
/// 依据消息的分组
/// CmdData 按 MsgName 分组后的视图。
/// </summary>
/// <remarks>
/// 该分组的遍历顺序决定了 <see cref="SchCanMsg"/> 中每个帧的 Index
/// 因而要求:调度表启动后,更新时必须按相同顺序遍历(当前实现保持一致)。
/// </remarks>
private IEnumerable<IGrouping<string, CanCmdData>> GroupMsg { get; set; }
/// <summary>
@@ -1111,7 +1244,7 @@ namespace CapMachine.Wpf.CanDrive
/// </summary>
public void UpdateValue()
{
//通过DBC进行对消息赋值
// 通过 DBC 生成每一帧的原始数据Data/DataLen/ID 等)
IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)));
int Index = 0;
//循环给MSG赋值数据
@@ -1152,8 +1285,18 @@ namespace CapMachine.Wpf.CanDrive
}
/// <summary>
/// 开始调度表执行
/// 启动硬件调度表
/// </summary>
/// <remarks>
/// 流程:
/// 1) 基于当前 <see cref="CmdData"/> 分组构建帧数组 <see cref="SchCanMsg"/>
/// 2) 使用 DBC 将信号值同步到帧;
/// 3) 根据 <see cref="ListCANScheduleConfig"/> 把周期写入帧的 TimeStamp 字段;
/// 4) 调用 CAN_SetSchedule 下发;
/// 5) 调用 CAN_StartSchedule 启动(当前约定只启动调度器 0
///
/// 注意:本实现把所有报文都放入调度表 0因此 UI 上的 SchTabIndex 实际未被使用。
/// </remarks>
public void StartSchedule()
{
if (CmdData.Count() == 0) return;
@@ -1181,13 +1324,13 @@ namespace CapMachine.Wpf.CanDrive
CAN_DBCParser.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
//每个分组就是一个帧指令/消息数据
SchCanMsg[Index] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CAN.CAN_MSG));
//分配当前消息帧在集合中的位置信息到ListCANScheduleConfig中方便实时更新时定位帧的位置
// 将“MsgName -> 帧 index”的映射写回调度表配置用于调试/定位(当前更新实现是全量更新,不依赖 MsgIndex
if (ListCANScheduleConfig.Any(a => a.MsgName == itemMsg.Key))
{
//把帧的位置给ListCANScheduleConfig方便更新时定位数据
ListCANScheduleConfig.FindFirst(a => a.MsgName == itemMsg.Key).MsgIndex = Index;
//设置当前这个报文的在调度表中的发送周期
// 设备 SDK 约定TimeStamp 字段用于调度表发送周期(单位毫秒)
SchCanMsg[Index].TimeStamp = (uint)ListCANScheduleConfig.FindFirst(a => a.MsgName == itemMsg.Key).Cycle;
}
Index++;
@@ -1222,7 +1365,7 @@ namespace CapMachine.Wpf.CanDrive
return;
}
//约定使用Index=0 1号调度器因为同一时刻只能一个调度器工作,把所有的报文都要放到这个调度器
// 约定使用调度器索引 0同一时刻只能运行一个调度器,且本项目把所有控制帧都集中在同一个调度器
//CAN_MSG的TimeStamp就是这个报文发送的周期由调度器协调
ret = USB2CAN.CAN_StartSchedule(DevHandle, WriteCANIndex, (byte)0, (byte)100, (byte)ListCANScheduleConfig.FirstOrDefault()!.OrderSend);
if (ret == USB2CAN.CAN_SUCCESS)
@@ -1269,8 +1412,11 @@ namespace CapMachine.Wpf.CanDrive
/// <summary>
/// 停止调度表
/// 停止硬件调度表
/// </summary>
/// <remarks>
/// 停止后设备侧将不再按周期自动发送。
/// </remarks>
public void StopSchedule()
{
ret = USB2CAN.CAN_StopSchedule(DevHandle, WriteCANIndex);//启动第一个调度表,表里面的CAN帧并行发送
@@ -1389,10 +1535,15 @@ namespace CapMachine.Wpf.CanDrive
/// <summary>
/// 加载要发送的数据
/// 一般是激活后才注册事件
/// 绑定/刷新驱动侧要发送的 CmdData并自动维护“值变化事件”的订阅。
/// </summary>
/// <param name="cmdData"></param>
/// <param name="cmdData">新的指令集合。</param>
/// <remarks>
/// 目的当某个信号值变化时CanCmdData.SignalCmdValue setter 触发),
/// 可以通过 <see cref="CmdData_CanCmdDataChangedHandler"/> 触发调度表增量刷新。
///
/// 注意:必须先对旧集合取消订阅,否则会造成重复触发和内存泄漏。
/// </remarks>
public void LoadCmdDataToDrive(List<CanCmdData> cmdData)
{
// Unsubscribe from events on the old CmdData items
@@ -1413,25 +1564,37 @@ namespace CapMachine.Wpf.CanDrive
}
/// <summary>
/// 指令数据发生变化执行方法
/// CmdData 中任意信号值变化事件处理。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <param name="sender">事件源。</param>
/// <param name="e">变化的 MsgName约定为报文名</param>
/// <remarks>
/// 当前实现采取“全量覆盖更新调度表”的方式UpdateSchedule并未按 MsgName 做局部更新。
/// </remarks>
private void CmdData_CanCmdDataChangedHandler(object? sender, string e)
{
UpdateSchDataByCmdDataChanged();
}
/// <summary>
/// 指令数据发生变化执行更新调度表锁
/// 调度表更新互斥锁。
/// </summary>
/// <remarks>
/// - CmdData 变化事件可能高频触发;
/// - 同时 UpdateSchedule 调用耗时且涉及非托管内存与 DBC 操作,必须串行化。
/// </remarks>
private readonly object SchUpdateLock = new object();
/// <summary>
/// 指令数据发生变化执行方法
/// 当 CmdData 中信号值变化时,增量刷新调度表的帧数据。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks>
/// 触发条件:<see cref="IsCycleSend"/> 与 <see cref="SchEnable"/> 同时为 true。
///
/// 当前实现策略:
/// - 为保证简单与一致性:每次变化都重建所有帧并全量调用 CAN_UpdateSchedule 覆盖更新;
/// - 若后续性能成为瓶颈,可根据变化的 MsgName 实现“按帧更新MsgIndex + MsgNum=1”。
/// </remarks>
private void UpdateSchDataByCmdDataChanged()
{
try
@@ -1447,7 +1610,7 @@ namespace CapMachine.Wpf.CanDrive
lock (SchUpdateLock)
{
//通过DBC进行对消息赋值
// 重新构建每一帧的 Data并覆盖更新到设备调度表
IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)));
int CycleUpdateIndex = 0;
//循环给MSG赋值数据顺序是固定的跟初始时设置是一样的
@@ -1468,7 +1631,7 @@ namespace CapMachine.Wpf.CanDrive
//通过DBC写入数据后生成CanMsg
//将信号值填入CAN消息里面
//释放申请的临时缓冲
// 释放非托管缓冲
Marshal.FreeHGlobal(msgPtSend);
//CAN_UpdateSchedule 官网解释
@@ -1479,7 +1642,7 @@ namespace CapMachine.Wpf.CanDrive
//CAN_UpdateSchedule中的MsgIndex表示当前的调度器中的帧Index序号
//因为调度表中的帧集合和控制帧的集合和要更新的帧集合都是同一个集合SchCanMsg
//默认1号调度表,一个更新所有的帧数
// 默认调度表索引 0一次性覆盖更新所有帧MsgIndex=0, MsgNum=帧数
var ret = USB2CAN.CAN_UpdateSchedule(DevHandle, WriteCANIndex, (byte)0, (byte)(0), SchCanMsg, (byte)SchCanMsg.Count());//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可
if (ret == USB2CAN.CAN_SUCCESS)
{
@@ -1511,8 +1674,14 @@ namespace CapMachine.Wpf.CanDrive
#endregion
/// <summary>
/// 循环获取CAN消息
/// 启动后台循环接收 CAN 报文,并同步到 DBC 信号实时值。
/// </summary>
/// <remarks>
/// 关键点:
/// - 使用 CAN_GetMsgWithSize 从设备内部 FIFO 拉取报文;
/// - 使用 <see cref="RecvMsgBufferPtr"/> 作为重用缓冲,避免每轮申请/释放非托管内存导致碎片与性能问题;
/// - 使用 <see cref="RecvBufferSync"/> 与 <see cref="CloseDevice"/> 互斥,避免指针并发释放。
/// </remarks>
public void StartCycleReviceCanMsg()
{
// 防止重复启动,若已有任务在运行则直接返回
@@ -1694,8 +1863,16 @@ namespace CapMachine.Wpf.CanDrive
/// <summary>
/// 关闭设备
/// 关闭设备并释放资源。
/// </summary>
/// <remarks>
/// 释放顺序要点:
/// - 先关闭设备并下置状态位;
/// - 若启用调度表则先停止;
/// - 停止定时器/周期发送;
/// - 等待接收任务退出(短等待,避免 UI 卡死);
/// - 在互斥锁内释放接收缓冲指针,避免接收线程仍在使用。
/// </remarks>
public void CloseDevice()
{
//关闭设备