更改
This commit is contained in:
@@ -22,7 +22,25 @@ namespace CapMachine.Wpf.CanDrive
|
||||
/// 但不是所有的SignalName都会配置一个Name,只是需要时才会配置名称
|
||||
/// 但是CanDbcModel集合会包括所有的SignalName名称的
|
||||
/// </summary>
|
||||
public string? Name { get; set; }
|
||||
private string? _name;
|
||||
|
||||
/// <summary>
|
||||
/// 配置的中文名称:速度,转速限制,使能等常用的信息数据
|
||||
/// 但不是所有的SignalName都会配置一个Name,只是需要时才会配置名称
|
||||
/// 但是CanDbcModel集合会包括所有的SignalName名称的
|
||||
/// </summary>
|
||||
public string? Name
|
||||
{
|
||||
get { return _name; }
|
||||
set
|
||||
{
|
||||
if (_name != value)
|
||||
{
|
||||
_name = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息名称
|
||||
|
||||
@@ -22,15 +22,35 @@ using System.Windows.Interop;
|
||||
namespace CapMachine.Wpf.CanDrive
|
||||
{
|
||||
/// <summary>
|
||||
/// Toomoss CANFD
|
||||
/// 图莫斯(Toomoss)CAN FD 驱动封装。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 职责边界:
|
||||
/// - 负责图莫斯 USB2XXX(CANFD)SDK 的设备扫描/打开/初始化/关闭;
|
||||
/// - 负责 DBC 文件解析,并提供“信号值 <-> CANFD 帧”的互转(通过 SDK 的 DBCParserByFD 接口);
|
||||
/// - 提供三类发送能力:
|
||||
/// - 单次发送(<see cref="SendCanMsg"/>);
|
||||
/// - 软件侧精确周期发送(<see cref="StartPrecisionCycleSendMsg"/>,以及并行版本 <see cref="StartParallelPrecisionCycleSendMsg"/>);
|
||||
/// - 硬件侧调度表发送(<see cref="StartSchedule"/> / <see cref="StopSchedule"/>,以及 <see cref="UpdateSchDataByCmdDataChanged"/> 增量更新);
|
||||
/// - 提供接收能力:后台轮询读取报文并同步到 DBC 信号实时值(<see cref="StartCycleReviceCanMsg"/>)。
|
||||
///
|
||||
/// 线程与资源:
|
||||
/// - 发送/接收/调度表更新均可能在后台线程运行;
|
||||
/// - 多处使用 <see cref="Marshal.AllocHGlobal"/> 分配非托管内存,必须在 finally 中释放,避免长期运行内存上涨。
|
||||
///
|
||||
/// 重要约束:
|
||||
/// - <see cref="DBCHandle"/> 为 0 表示 DBC 未解析成功,任何 DBC_* 调用都应视为不可用;
|
||||
/// - <see cref="WriteCANIndex"/> / <see cref="ReadCANIndex"/> 表示 CANFD 通道索引(0=CAN1,1=CAN2)。
|
||||
/// </remarks>
|
||||
public class ToomossCanFD : BindableBase
|
||||
{
|
||||
private readonly IContainerProvider ContainerProvider;
|
||||
|
||||
/// <summary>
|
||||
/// 实例化函数
|
||||
/// 构造函数。
|
||||
/// </summary>
|
||||
/// <param name="containerProvider">DI 容器。</param>
|
||||
/// <param name="logService">日志服务。</param>
|
||||
public ToomossCanFD(IContainerProvider containerProvider, ILogService logService)
|
||||
{
|
||||
ContainerProvider = containerProvider;
|
||||
@@ -49,8 +69,19 @@ namespace CapMachine.Wpf.CanDrive
|
||||
public ILogService LoggerService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始CAN的驱动
|
||||
/// 启动 CANFD 驱动(设备准备流程)。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 执行顺序与 SDK 依赖一致:
|
||||
/// 1) 校验 DLL 是否存在;
|
||||
/// 2) 扫描设备并选取句柄;
|
||||
/// 3) 打开设备;
|
||||
/// 4) 读取设备信息;
|
||||
/// 5) 获取 CANFD 初始化配置(仲裁/数据波特率);
|
||||
/// 6) 初始化 CANFD。
|
||||
///
|
||||
/// 注意:该方法不解析 DBC;DBC 解析由 <see cref="StartDbc"/> 显式触发。
|
||||
/// </remarks>
|
||||
public void StartCanDrive()
|
||||
{
|
||||
try
|
||||
@@ -70,8 +101,10 @@ namespace CapMachine.Wpf.CanDrive
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始Dbc文件写入
|
||||
/// 解析并加载 DBC 文件。
|
||||
/// </summary>
|
||||
/// <param name="DbcPath">DBC 文件路径。</param>
|
||||
/// <returns>解析得到的信号列表(用于 UI 展示与实时值刷新)。</returns>
|
||||
public ObservableCollection<CanDbcModel> StartDbc(string DbcPath)
|
||||
{
|
||||
DBC_Parser(DbcPath);
|
||||
@@ -79,36 +112,44 @@ namespace CapMachine.Wpf.CanDrive
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dbc消息集合
|
||||
/// DBC 解析得到的信号集合。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 接收线程会持续更新 <see cref="CanDbcModel.SignalRtValue"/>。
|
||||
/// </remarks>
|
||||
public ObservableCollection<CanDbcModel> ListCanFdDbcModel { get; set; } = new ObservableCollection<CanDbcModel>();
|
||||
|
||||
|
||||
#region 公共变量
|
||||
|
||||
/// <summary>
|
||||
/// 仲裁波特率
|
||||
/// 仲裁波特率(Arbitration Bit Rate)。
|
||||
/// </summary>
|
||||
public uint ArbBaudRate { get; set; } = 500000;
|
||||
|
||||
/// <summary>
|
||||
/// 数据波特率
|
||||
/// 数据波特率(Data Bit Rate)。
|
||||
/// </summary>
|
||||
public uint DataBaudRate { get; set; } = 2000000;
|
||||
|
||||
/// <summary>
|
||||
/// CAN FD标准 ISO是否启用
|
||||
/// 是否启用 ISO CRC(CAN FD ISO 标准)。
|
||||
/// </summary>
|
||||
public bool ISOEnable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 终端电阻 是否启用
|
||||
/// 终端电阻是否启用。
|
||||
/// </summary>
|
||||
public bool ResEnable { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 更新配置
|
||||
/// 更新 CANFD 参数配置(仅更新内存变量,实际生效需重新 InitCAN)。
|
||||
/// </summary>
|
||||
/// <param name="arbBaudRate">仲裁波特率。</param>
|
||||
/// <param name="dataBaudRate">数据波特率。</param>
|
||||
/// <param name="iSOEnable">ISO CRC 使能。</param>
|
||||
/// <param name="resEnable">终端电阻使能。</param>
|
||||
/// <param name="sendCycle">软件循环发送周期(ms)。</param>
|
||||
public void UpdateConfig(uint arbBaudRate, uint dataBaudRate, bool iSOEnable, bool resEnable, ushort sendCycle)
|
||||
{
|
||||
ArbBaudRate = arbBaudRate;
|
||||
@@ -139,8 +180,11 @@ namespace CapMachine.Wpf.CanDrive
|
||||
public USB2CANFD.CANFD_BUS_ERROR CurCANFD_BUS_ERROR = new USB2CANFD.CANFD_BUS_ERROR();
|
||||
|
||||
/// <summary>
|
||||
/// DBC Handle
|
||||
/// DBC 解析成功后得到的句柄。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 为 0 表示 DBC 未解析/解析失败。
|
||||
/// </remarks>
|
||||
public UInt64 DBCHandle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -154,19 +198,19 @@ namespace CapMachine.Wpf.CanDrive
|
||||
public Int32 DevHandle { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Write CAN Index
|
||||
/// 发送通道索引(0=CAN1,1=CAN2)。
|
||||
/// </summary>
|
||||
public Byte WriteCANIndex { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Read CAN Index
|
||||
/// 接收通道索引(0=CAN1,1=CAN2)。
|
||||
/// </summary>
|
||||
public Byte ReadCANIndex { get; set; } = 0;
|
||||
|
||||
|
||||
private bool _OpenState;
|
||||
/// <summary>
|
||||
/// 打开设备的状态
|
||||
/// 设备打开状态。
|
||||
/// </summary>
|
||||
public bool OpenState
|
||||
{
|
||||
@@ -176,7 +220,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
|
||||
private bool _DbcParserState;
|
||||
/// <summary>
|
||||
/// DBC解析的状态
|
||||
/// DBC 解析状态。
|
||||
/// </summary>
|
||||
public bool DbcParserState
|
||||
{
|
||||
@@ -405,8 +449,12 @@ namespace CapMachine.Wpf.CanDrive
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送CAN数据
|
||||
/// 单次发送:根据 CmdData 中的信号值构建 CANFD 报文并发送。
|
||||
/// </summary>
|
||||
/// <param name="CmdData">待发送的信号指令集合(按 MsgName 分组后组成帧)。</param>
|
||||
/// <remarks>
|
||||
/// 注意:该方法会分配临时非托管内存 msgPt,并在结束前释放。
|
||||
/// </remarks>
|
||||
public void SendCanMsg(List<CanCmdData> CmdData)
|
||||
{
|
||||
var GroupMsg = CmdData.GroupBy(x => x.MsgName);
|
||||
@@ -417,6 +465,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
CanMsg[i].Data = new Byte[64];
|
||||
}
|
||||
|
||||
// 非托管缓冲:SDK API 以指针形式写入帧结构体
|
||||
IntPtr msgPt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG)));
|
||||
int Index = 0;
|
||||
//循环给MSG赋值数据
|
||||
@@ -450,7 +499,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
//DBCParser.DBC_SyncValueToCANFDMsg(DBCHandle, new StringBuilder("msg_speed_can"), msgPt);
|
||||
//CanMsg[2] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPt, typeof(USB2CANFD.CANFD_MSG));
|
||||
|
||||
//释放申请的临时缓冲区
|
||||
// 释放临时非托管缓冲区,避免内存泄漏
|
||||
Marshal.FreeHGlobal(msgPt);
|
||||
Console.WriteLine("");
|
||||
//发送CAN数据
|
||||
@@ -492,8 +541,13 @@ namespace CapMachine.Wpf.CanDrive
|
||||
private static Task CycleUpdateCmdTask { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 精确周期发送CAN数据
|
||||
/// 软件侧精确周期发送 CANFD 数据。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 使用 <see cref="Stopwatch"/> tick 作为时间基准,减少 DateTime 抖动;
|
||||
/// - 使用 Task.Delay + SpinWait 组合等待;
|
||||
/// - 使用 <see cref="CycleSendCts"/> 支持外部取消。
|
||||
/// </remarks>
|
||||
public void StartPrecisionCycleSendMsg()
|
||||
{
|
||||
// 创建取消标记源 用于控制任务的取消 允许在需要时通过取消令牌来优雅停止任务
|
||||
@@ -580,6 +634,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
CanMsg[i].Data = new Byte[64];
|
||||
}
|
||||
|
||||
// 发送构帧临时缓冲:每轮申请/释放,避免与其他线程共享同一指针导致并发问题
|
||||
IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG)));
|
||||
int Index = 0;
|
||||
|
||||
@@ -598,7 +653,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
//通过DBC写入数据后生成CanMsg
|
||||
//将信号值填入CAN消息里面
|
||||
|
||||
//释放申请的临时缓冲区
|
||||
// 释放非托管缓冲区(务必释放,否则长时间运行会造成内存泄漏)
|
||||
Marshal.FreeHGlobal(msgPtSend);
|
||||
//CanMsg[0].Flags = 5;
|
||||
|
||||
@@ -682,8 +737,12 @@ namespace CapMachine.Wpf.CanDrive
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改停止发送的方法
|
||||
/// 停止软件循环发送。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 置 <see cref="IsCycleSend"/> 为 false,循环自然退出;
|
||||
/// - 触发 <see cref="CycleSendCts"/> 取消,用于尽快唤醒 Delay 并退出。
|
||||
/// </remarks>
|
||||
public void StopCycleSendMsg()
|
||||
{
|
||||
IsCycleSend = false;
|
||||
@@ -871,8 +930,11 @@ namespace CapMachine.Wpf.CanDrive
|
||||
|
||||
private bool _SchEnable;
|
||||
/// <summary>
|
||||
/// 调度表使能
|
||||
/// 调度表使能开关。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该开关用于控制“硬件调度表发送”及“CmdData 变化时是否增量更新调度表”。
|
||||
/// </remarks>
|
||||
public bool SchEnable
|
||||
{
|
||||
get { return _SchEnable; }
|
||||
@@ -977,8 +1039,16 @@ namespace CapMachine.Wpf.CanDrive
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始调度表执行
|
||||
/// 启动硬件调度表(CANFD)。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 流程与 CAN 类似:
|
||||
/// 1) 基于 <see cref="CmdData"/> 分组构建帧数组;
|
||||
/// 2) 使用 DBC 将信号值同步到帧;
|
||||
/// 3) 将调度周期写入帧 <see cref="USB2CANFD.CANFD_MSG.TimeStamp"/>;
|
||||
/// 4) 调用 CANFD_SetSchedule 下发;
|
||||
/// 5) 调用 CANFD_StartSchedule 启动调度器 0。
|
||||
/// </remarks>
|
||||
public void StartSchedule()
|
||||
{
|
||||
if (CmdData.Count() == 0) return;
|
||||
@@ -1006,13 +1076,13 @@ namespace CapMachine.Wpf.CanDrive
|
||||
DBCParserByFD.DBC_SyncValueToCANFDMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
|
||||
//每个分组就是一个帧指令/消息数据
|
||||
SchCanMsg[Index] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CANFD.CANFD_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++;
|
||||
@@ -1047,7 +1117,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
return;
|
||||
}
|
||||
|
||||
//约定使用Index=0 1号调度器,因为同一个时刻只能有一个调度器工作,把所有的报文都要放到这个调度器中
|
||||
// 约定使用调度器索引 0:同一时刻只能运行一个调度器,且本项目把所有控制帧都集中在同一个调度器里
|
||||
//CAN_MSG的TimeStamp就是这个报文发送的周期,由调度器协调
|
||||
ret = USB2CANFD.CANFD_StartSchedule(DevHandle, WriteCANIndex, (byte)0, (byte)100, (byte)ListCANScheduleConfig.FirstOrDefault()!.OrderSend);
|
||||
if (ret == USB2CANFD.CANFD_SUCCESS)
|
||||
@@ -1094,8 +1164,11 @@ namespace CapMachine.Wpf.CanDrive
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 停止调度表
|
||||
/// 停止硬件调度表。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 停止后设备侧将不再按周期自动发送。
|
||||
/// </remarks>
|
||||
public void StopSchedule()
|
||||
{
|
||||
ret = USB2CANFD.CANFD_StopSchedule(DevHandle, WriteCANIndex);//启动第一个调度表,表里面的CAN帧并行发送
|
||||
@@ -1121,10 +1194,12 @@ namespace CapMachine.Wpf.CanDrive
|
||||
private int CycleUpdateIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 加载要发送的数据
|
||||
/// 一般是激活后才注册事件
|
||||
/// 绑定/刷新驱动侧要发送的 CmdData,并自动维护“值变化事件”的订阅。
|
||||
/// </summary>
|
||||
/// <param name="cmdData"></param>
|
||||
/// <param name="cmdData">新的指令集合。</param>
|
||||
/// <remarks>
|
||||
/// 注意:必须先对旧集合取消订阅,否则会造成重复触发和内存泄漏。
|
||||
/// </remarks>
|
||||
public void LoadCmdDataToDrive(List<CanCmdData> cmdData)
|
||||
{
|
||||
// Unsubscribe from events on the old CmdData items
|
||||
@@ -1145,25 +1220,27 @@ namespace CapMachine.Wpf.CanDrive
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指令数据发生变化执行方法
|
||||
/// CmdData 中任意信号值变化事件处理。
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <param name="sender">事件源。</param>
|
||||
/// <param name="e">变化的 MsgName(约定为报文名)。</param>
|
||||
private void CmdData_CanCmdDataChangedHandler(object? sender, string e)
|
||||
{
|
||||
UpdateSchDataByCmdDataChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指令数据发生变化执行更新调度表锁
|
||||
/// 调度表更新互斥锁。
|
||||
/// </summary>
|
||||
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。
|
||||
/// 当前实现为“全量覆盖更新”(CANFD_UpdateSchedule)。
|
||||
/// </remarks>
|
||||
public void UpdateSchDataByCmdDataChanged()
|
||||
{
|
||||
try
|
||||
@@ -1179,7 +1256,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
|
||||
lock (SchUpdateLock)
|
||||
{
|
||||
//通过DBC进行对消息赋值
|
||||
// 重新构建每一帧的 Data,并覆盖更新到设备调度表
|
||||
IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG)));
|
||||
int CycleUpdateIndex = 0;
|
||||
//循环给MSG赋值数据,顺序是固定的,跟初始时设置是一样的
|
||||
@@ -1200,7 +1277,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
//通过DBC写入数据后生成CanMsg
|
||||
//将信号值填入CAN消息里面
|
||||
|
||||
//释放申请的临时缓冲区
|
||||
// 释放非托管缓冲
|
||||
Marshal.FreeHGlobal(msgPtSend);
|
||||
|
||||
//CAN_UpdateSchedule 官网解释
|
||||
@@ -1211,7 +1288,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
|
||||
//CAN_UpdateSchedule中的MsgIndex表示当前的调度器中的帧Index序号
|
||||
//因为调度表中的帧集合和控制帧的集合和要更新的帧集合都是同一个集合SchCanMsg
|
||||
//默认1号调度表,一个更新所有的帧数据
|
||||
// 默认调度表索引 0:一次性覆盖更新所有帧(MsgIndex=0, MsgNum=帧数)
|
||||
var ret = USB2CANFD.CANFD_UpdateSchedule(DevHandle, WriteCANIndex, (byte)0, (byte)(0), SchCanMsg, (byte)SchCanMsg.Count());//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可
|
||||
if (ret == USB2CANFD.CANFD_SUCCESS)
|
||||
{
|
||||
@@ -1247,7 +1324,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
|
||||
private bool _IsCycleRevice;
|
||||
/// <summary>
|
||||
/// 是否循环接收数据
|
||||
/// 是否循环接收数据。
|
||||
/// </summary>
|
||||
public bool IsCycleRevice
|
||||
{
|
||||
@@ -1258,7 +1335,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
|
||||
private bool _IsCycleSend;
|
||||
/// <summary>
|
||||
/// 是否循环发送数据
|
||||
/// 是否循环发送数据。
|
||||
/// </summary>
|
||||
public bool IsCycleSend
|
||||
{
|
||||
@@ -1267,12 +1344,12 @@ namespace CapMachine.Wpf.CanDrive
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 循环发送数据
|
||||
/// 软件循环发送周期(毫秒)。
|
||||
/// </summary>
|
||||
public ushort SendCycle { get; set; } = 200;
|
||||
|
||||
/// <summary>
|
||||
/// 循环接受数据
|
||||
/// 接收轮询周期(毫秒)。
|
||||
/// </summary>
|
||||
public ushort ReviceCycle { get; set; } = 200;
|
||||
|
||||
@@ -1292,7 +1369,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
|
||||
private bool _IsSendOk;
|
||||
/// <summary>
|
||||
/// 发送报文是否OK
|
||||
/// 最近一次发送是否成功。
|
||||
/// </summary>
|
||||
public bool IsSendOk
|
||||
{
|
||||
@@ -1310,7 +1387,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
|
||||
private bool _IsReviceOk;
|
||||
/// <summary>
|
||||
/// 接收报文是否OK
|
||||
/// 最近一次接收是否成功。
|
||||
/// </summary>
|
||||
public bool IsReviceOk
|
||||
{
|
||||
@@ -1327,13 +1404,17 @@ namespace CapMachine.Wpf.CanDrive
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 要发送的数据
|
||||
/// 当前激活的指令集合(由 Service/UI 下发)。
|
||||
/// </summary>
|
||||
public List<CanCmdData> CmdData { get; set; } = new List<CanCmdData>();
|
||||
|
||||
/// <summary>
|
||||
/// 循环获取CAN消息
|
||||
/// 启动后台循环接收 CANFD 报文,并同步到 DBC 信号实时值。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 注意:当前实现每轮接收都会分配 msgPtRead 并释放,长时间运行会造成 GC 压力和潜在碎片;
|
||||
/// 若后续需要优化,可参考 CAN 版本的“接收缓冲池复用 + CloseDevice 互斥释放”。
|
||||
/// </remarks>
|
||||
public void StartCycleReviceCanMsg()
|
||||
{
|
||||
CycleReviceTask = Task.Run(async () =>
|
||||
@@ -1403,7 +1484,7 @@ namespace CapMachine.Wpf.CanDrive
|
||||
//}
|
||||
}
|
||||
|
||||
//释放数据缓冲区,必须释放,否则程序运行一段时间后会报内存不足
|
||||
// 释放数据缓冲区,必须释放,否则程序运行一段时间后会报内存不足
|
||||
Marshal.FreeHGlobal(msgPtRead);
|
||||
Thread.Sleep(10);
|
||||
|
||||
@@ -1477,8 +1558,14 @@ namespace CapMachine.Wpf.CanDrive
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 关闭设备
|
||||
/// 关闭设备并释放资源。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 释放顺序要点:
|
||||
/// - 关闭设备并下置状态位;
|
||||
/// - 若启用调度表则先停止;
|
||||
/// - 等待接收任务退出(短等待,避免 UI 卡死)。
|
||||
/// </remarks>
|
||||
public void CloseDevice()
|
||||
{
|
||||
//关闭设备
|
||||
|
||||
@@ -13,15 +13,38 @@ using System.Text;
|
||||
namespace CapMachine.Wpf.CanDrive
|
||||
{
|
||||
/// <summary>
|
||||
/// Toomoss CAN
|
||||
/// 图莫斯(Toomoss)CAN 驱动封装。
|
||||
/// </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。
|
||||
///
|
||||
/// 注意:该方法不解析 DBC;DBC 解析由 <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 > 0:成功收到数据;
|
||||
/// - CanNum == 0:本轮无数据;
|
||||
/// - CanNum < 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()
|
||||
{
|
||||
//关闭设备
|
||||
|
||||
@@ -2,10 +2,22 @@
|
||||
|
||||
namespace CapMachine.Wpf.CanDrive.ZlgCan
|
||||
{
|
||||
/// <summary>
|
||||
/// ZLG 原生驱动(zlgcan.dll)P/Invoke 映射层。
|
||||
/// 说明:
|
||||
/// - 本类仅负责将厂商 SDK 的函数/常量/结构体映射到 C#,不包含任何业务逻辑;上层 Driver/Service 会对这些 API 进行封装并提供线程安全与资源管理。
|
||||
/// - 互操作注意事项:
|
||||
/// 1) <c>zlgcan.dll</c> 必须随程序部署且位数与进程一致(x86/x64),否则会在加载/调用时失败。
|
||||
/// 2) 结构体需严格按 SDK 定义设置 <see cref="StructLayoutAttribute"/> 与 Pack;字段顺序与大小必须一致。
|
||||
/// 3) DllImport 的 CallingConvention 与 SetLastError 需与 SDK 匹配,否则可能出现栈不平衡或错误码不可用。
|
||||
/// - 约定:当前工程中通常由 <c>ZlgCanFd200uDriver</c> 负责检查 DLL 存在与架构匹配,并在打开设备前完成预检查。
|
||||
/// </summary>
|
||||
public class ZLGCAN
|
||||
{
|
||||
|
||||
#region 设备类型
|
||||
// 设备类型常量:用于 ZCAN_OpenDevice/ZCAN_OpenDeviceByName。
|
||||
// 说明:常量值来自厂商 SDK;此处保持原值以便与文档/示例对照。
|
||||
public static UInt32 ZCAN_PCI9810 = 2;
|
||||
public static UInt32 ZCAN_USBCAN1 = 3;
|
||||
public static UInt32 ZCAN_USBCAN2 = 4;
|
||||
@@ -82,12 +94,16 @@ namespace CapMachine.Wpf.CanDrive.ZlgCan
|
||||
#endregion
|
||||
|
||||
#region LIN事件
|
||||
// LIN 事件常量:用于接收/状态通知。
|
||||
public static UInt32 ZCAN_LIN_WAKE_UP = 1;
|
||||
public static UInt32 ZCAN_LIN_ENTERED_SLEEP_MODE = 2;
|
||||
public static UInt32 ZCAN_LIN_EXITED_SLEEP_MODE = 3;
|
||||
#endregion
|
||||
|
||||
#region 函数
|
||||
// 原生函数导入:
|
||||
// - 这里不逐个函数详注,具体语义以 SDK 文档为准;
|
||||
// - 上层 Driver 会封装为更易用的 C# API,并处理错误码、重试、线程模型等。
|
||||
[DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
||||
public static extern IntPtr ZCAN_OpenDevice(uint device_type, uint device_index, uint reserved);
|
||||
|
||||
@@ -230,6 +246,8 @@ namespace CapMachine.Wpf.CanDrive.ZlgCan
|
||||
#endregion
|
||||
|
||||
#region 结构体
|
||||
// SDK 结构体定义:用于与原生内存布局一致地传参/收参。
|
||||
// 说明:字段注释多数来自 SDK 示例或历史代码;这里主要保证布局与 Pack 正确。
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct ZCAN_DEVICE_INFO
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user