Files
CapMachine/CapMachine.Wpf/CanDrive/CanFD/ToomossCanFD.cs
2026-04-01 20:25:46 +08:00

1600 lines
69 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 CapMachine.Wpf.CanDrive.CanFD;
using CapMachine.Wpf.Dtos;
using CapMachine.Wpf.Models.Tag;
using CapMachine.Wpf.Services;
using ImTools;
using NPOI.OpenXmlFormats.Wordprocessing;
using Prism.Ioc;
using Prism.Mvvm;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Interop;
namespace CapMachine.Wpf.CanDrive
{
/// <summary>
/// 图莫斯ToomossCAN FD 驱动封装。
/// </summary>
/// <remarks>
/// 职责边界:
/// - 负责图莫斯 USB2XXXCANFDSDK 的设备扫描/打开/初始化/关闭;
/// - 负责 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;
LoggerService = logService;
HighSpeedDataService = ContainerProvider.Resolve<HighSpeedDataService>();
//LogService = ContainerProvider.Resolve<ILogService>();
//Stopwatch.Frequency表示高精度计时器每秒的计数次数ticks/秒每毫秒的ticks数 = 每秒的ticks数 ÷ 1000
TicksPerMs = Stopwatch.Frequency / 1000.0;
}
/// <summary>
/// HighSpeedDataService 实例
/// </summary>
public HighSpeedDataService HighSpeedDataService { get; set; }
public ILogService LoggerService { get; set; }
/// <summary>
/// 启动 CANFD 驱动(设备准备流程)。
/// </summary>
/// <remarks>
/// 执行顺序与 SDK 依赖一致:
/// 1) 校验 DLL 是否存在;
/// 2) 扫描设备并选取句柄;
/// 3) 打开设备;
/// 4) 读取设备信息;
/// 5) 获取 CANFD 初始化配置(仲裁/数据波特率);
/// 6) 初始化 CANFD。
///
/// 注意:该方法不解析 DBCDBC 解析由 <see cref="StartDbc"/> 显式触发。
/// </remarks>
public void StartCanDrive()
{
try
{
IsExistsDllFile();
ScanDevice();
OpenDevice();
GetDeviceInfo();
GetCANConfig();
InitCAN();
}
catch (Exception ex)
{
LoggerService.Error(ex.Message);
System.Windows.MessageBox.Show($"{ex.Message}", "提示", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Hand);
}
}
/// <summary>
/// 解析并加载 DBC 文件。
/// </summary>
/// <param name="DbcPath">DBC 文件路径。</param>
/// <returns>解析得到的信号列表(用于 UI 展示与实时值刷新)。</returns>
public ObservableCollection<CanDbcModel> StartDbc(string DbcPath)
{
DBC_Parser(DbcPath);
return ListCanFdDbcModel;
}
/// <summary>
/// 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>
/// 是否启用 ISO CRCCAN 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;
DataBaudRate = dataBaudRate;
ISOEnable = iSOEnable;
ResEnable = resEnable;
SendCycle = sendCycle;
}
/// <summary>
/// 设备固件信息
/// </summary>
public USB_DEVICEByFD.DEVICE_INFO DevInfo = new USB_DEVICEByFD.DEVICE_INFO();
/// <summary>
/// CAN Config
/// </summary>
USB2CANFD.CANFD_INIT_CONFIG CANConfig = new USB2CANFD.CANFD_INIT_CONFIG();
/// <summary>
/// 诊断信息
/// </summary>
public USB2CANFD.CANFD_DIAGNOSTIC CurCANFD_DIAGNOSTIC = new USB2CANFD.CANFD_DIAGNOSTIC();
/// <summary>
/// CANFD总线错误信息
/// </summary>
public USB2CANFD.CANFD_BUS_ERROR CurCANFD_BUS_ERROR = new USB2CANFD.CANFD_BUS_ERROR();
/// <summary>
/// DBC 解析成功后得到的句柄。
/// </summary>
/// <remarks>
/// 为 0 表示 DBC 未解析/解析失败。
/// </remarks>
public UInt64 DBCHandle { get; set; }
/// <summary>
/// 扫描设备Handle集合
/// </summary>
public Int32[] DevHandles { get; set; } = new Int32[20];
/// <summary>
/// 扫描设备Handle
/// </summary>
public Int32 DevHandle { get; set; } = 0;
/// <summary>
/// 发送通道索引0=CAN1,1=CAN2
/// </summary>
public Byte WriteCANIndex { get; set; } = 0;
/// <summary>
/// 接收通道索引0=CAN1,1=CAN2
/// </summary>
public Byte ReadCANIndex { get; set; } = 0;
private bool _OpenState;
/// <summary>
/// 设备打开状态。
/// </summary>
public bool OpenState
{
get { return _OpenState; }
set { _OpenState = value; RaisePropertyChanged(); }
}
private bool _DbcParserState;
/// <summary>
/// DBC 解析状态。
/// </summary>
public bool DbcParserState
{
get { return _DbcParserState; }
set { _DbcParserState = value; RaisePropertyChanged(); }
}
/// <summary>
/// 扫描到设备个数
/// </summary>
public Int32 DevNum { get; set; }
public Int32 ret { get; set; }
public string dllFilePath { get; set; } = "USB2XXX.dll";
/// <summary>
/// 消息值Pt
/// </summary>
public IntPtr msgPt { get; set; }
#endregion
/// <summary>
/// ******************【1】*********************
/// 是否存在Dll文件
/// </summary>
/// <returns></returns>
public bool IsExistsDllFile()
{
if (!File.Exists(dllFilePath))
{
Console.WriteLine("请先将USB2XXX.dll和libusb-1.0.dll文件复制到exe程序文件输出目录下!");
Console.WriteLine("dll文件在usb2can_lin_pwm_example/sdk/libs/windows目录下");
Console.WriteLine("程序是32位的就复制x86目录下文件程序是64位的就复制x86_64目录下文件");
return false;
}
return true;
}
/// <summary>
/// ******************【2】*********************
/// 扫描查找设备,并将每个设备的唯一设备号存放到数组中,后面的函数需要用到
/// </summary>
/// <returns></returns>
public bool ScanDevice()
{
DevNum = USB_DEVICEByFD.USB_ScanDevice(DevHandles);
if (DevNum <= 0)
{
Console.WriteLine("No device connected!");
return false;
}
else
{
Console.WriteLine("Have {0} device connected!", DevNum);
DevHandle = DevHandles[0];//获取第一个设备的设备号
return true;
}
}
/// <summary>
/// ******************【3】*********************
/// 打开设备
/// </summary>
/// <returns></returns>
public bool OpenDevice()
{
//打开设备
OpenState = USB_DEVICEByFD.USB_OpenDevice(DevHandle);
if (!OpenState)
{
Console.WriteLine("Open device error!");
return false;
}
else
{
Console.WriteLine("Open device success!");
return true;
}
}
/// <summary>
/// ******************【4】*********************
/// 获取设备的固件信息
/// </summary>
/// <returns></returns>
public bool GetDeviceInfo()
{
//获取固件信息
StringBuilder FuncStr = new StringBuilder(256);
OpenState = USB_DEVICEByFD.DEV_GetDeviceInfo(DevHandle, ref DevInfo, FuncStr);
if (!OpenState)
{
Console.WriteLine("Get device infomation error!");
return false;
}
else
{
Console.WriteLine("Firmware Info:");
Console.WriteLine(" Name:" + Encoding.Default.GetString(DevInfo.FirmwareName));
Console.WriteLine(" Build Date:" + Encoding.Default.GetString(DevInfo.BuildDate));
Console.WriteLine(" Firmware Version:v{0}.{1}.{2}", (DevInfo.FirmwareVersion >> 24) & 0xFF, (DevInfo.FirmwareVersion >> 16) & 0xFF, DevInfo.FirmwareVersion & 0xFFFF);
Console.WriteLine(" Hardware Version:v{0}.{1}.{2}", (DevInfo.HardwareVersion >> 24) & 0xFF, (DevInfo.HardwareVersion >> 16) & 0xFF, DevInfo.HardwareVersion & 0xFFFF);
Console.WriteLine(" Functions:" + DevInfo.Functions.ToString("X8"));
Console.WriteLine(" SerialNumber:" + DevInfo.SerialNumber[0] + DevInfo.SerialNumber[1] + DevInfo.SerialNumber[2]);
Console.WriteLine(" Functions String:" + FuncStr);
StringBuilder DLLBuildDate = new StringBuilder(256);
USB_DEVICEByFD.DEV_GetDllBuildTime(DLLBuildDate);
Console.WriteLine(" DLL Build Date:" + DLLBuildDate);
return true;
}
}
/// <summary>
/// ******************【5】*********************
/// 获取设备Config配置
/// </summary>
public void GetCANConfig()
{
//获取CAN波特率参数
ret = USB2CANFD.CANFD_GetCANSpeedArg(DevHandle, ref CANConfig, ArbBaudRate, DataBaudRate);
if (ret != USB2CANFD.CANFD_SUCCESS)
{
Console.WriteLine("Get CAN Speed failed!");
return;
}
else
{
Console.WriteLine("Get CAN Speed Success!");
}
}
/// <summary>
/// ******************【6】*********************
/// 初始化CAN
/// </summary>
public void InitCAN()
{
//初始化CAN
CANConfig.ISOCRCEnable = ISOEnable == true ? (byte)1 : (byte)0;//使能ISOCRC
CANConfig.ResEnable = ResEnable == true ? (byte)1 : (byte)0;//使能终端电阻
ret = USB2CANFD.CANFD_Init(DevHandle, WriteCANIndex, ref CANConfig);
if (ret != USB2CANFD.CANFD_SUCCESS)
{
Console.WriteLine("Config CAN failed!");
return;
}
else
{
Console.WriteLine("WriteCANIndex Config CAN Success!");
}
ret = USB2CANFD.CANFD_Init(DevHandle, ReadCANIndex, ref CANConfig);
if (ret != USB2CANFD.CANFD_SUCCESS)
{
Console.WriteLine("Config CAN failed!");
return;
}
else
{
Console.WriteLine("ReadCANIndex Config CAN Success!");
}
Console.WriteLine("");
}
/// <summary>
/// ******************【7】*********************
/// DBC解析
/// </summary>
public void DBC_Parser(string Path)
{
//解析DBC文件
DBCHandle = DBCParserByFD.DBC_ParserFile(DevHandle, new StringBuilder(Path));
if (DBCHandle == 0)
{
Console.WriteLine("Parser DBC File error!");
DbcParserState = false;
return;
}
else
{
Console.WriteLine("Parser DBC File success!");
}
ListCanFdDbcModel.Clear();
//打印DBC里面报文和信号相关信息
int DBCMsgNum = DBCParserByFD.DBC_GetMsgQuantity(DBCHandle);
for (int i = 0; i < DBCMsgNum; i++)
{
StringBuilder MsgName = new StringBuilder(32);
DBCParserByFD.DBC_GetMsgName(DBCHandle, i, MsgName);
Console.WriteLine("Msg.Name = {0}", MsgName);
int DBCSigNum = DBCParserByFD.DBC_GetMsgSignalQuantity(DBCHandle, MsgName);
StringBuilder Publisher = new StringBuilder(32);
DBCParserByFD.DBC_GetMsgPublisher(DBCHandle, MsgName, Publisher);
Console.Write("Signals:");
for (int j = 0; j < DBCSigNum; j++)
{
StringBuilder SigName = new StringBuilder(32);
DBCParserByFD.DBC_GetMsgSignalName(DBCHandle, MsgName, j, SigName);
Console.Write("{0} ", SigName);
//增加信息数据
ListCanFdDbcModel.Add(new CanDbcModel()
{
MsgName = MsgName.ToString(),
MsgId = "",
SignalName = SigName.ToString(),
SignalDesc = "",
SignalUnit = "",
SignalRtValue = "",
Publisher = Publisher.ToString()
});
}
Console.WriteLine("");
}
//Dbc解析成功
DbcParserState = true;
}
/// <summary>
/// 单次发送:根据 CmdData 中的信号值构建 CANFD 报文并发送。
/// </summary>
/// <param name="CmdData">待发送的信号指令集合(按 MsgName 分组后组成帧)。</param>
/// <remarks>
/// 注意:该方法会分配临时非托管内存 msgPt并在结束前释放。
/// </remarks>
public void SendCanMsg(List<CanCmdData> CmdData)
{
var GroupMsg = CmdData.GroupBy(x => x.MsgName);
USB2CANFD.CANFD_MSG[] CanMsg = new USB2CANFD.CANFD_MSG[GroupMsg.Count()];
for (int i = 0; i < GroupMsg.Count(); i++)
{
CanMsg[i] = new USB2CANFD.CANFD_MSG();
CanMsg[i].Data = new Byte[64];
}
// 非托管缓冲SDK API 以指针形式写入帧结构体
IntPtr msgPt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG)));
int Index = 0;
//循环给MSG赋值数据
foreach (var itemMsg in GroupMsg)
{
foreach (var itemSignal in itemMsg)
{
DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
}
DBCParserByFD.DBC_SyncValueToCANFDMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPt);
CanMsg[Index] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPt, typeof(USB2CANFD.CANFD_MSG));
Index++;
}
//设置信号值
//DBCParser.DBC_SetSignalValue(DBCHandle, new StringBuilder("msg_moto_speed"), new StringBuilder("moto_speed"), 2412);
//DBCParser.DBC_SetSignalValue(DBCHandle, new StringBuilder("msg_oil_pressure"), new StringBuilder("oil_pressure"), 980);
//DBCParser.DBC_SetSignalValue(DBCHandle, new StringBuilder("msg_speed_can"), new StringBuilder("speed_can"), 120);
//通过DBC写入数据后生成CanMsg
//将信号值填入CAN消息里面
//IntPtr msgPt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG)));
//DBCParser.DBC_SyncValueToCANFDMsg(DBCHandle, new StringBuilder("msg_moto_speed"), msgPt);
//CanMsg[0] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPt, typeof(USB2CANFD.CANFD_MSG));
//DBCParser.DBC_SyncValueToCANFDMsg(DBCHandle, new StringBuilder("msg_oil_pressure"), msgPt);
//CanMsg[1] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPt, typeof(USB2CANFD.CANFD_MSG));
//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数据
int SendedNum = USB2CANFD.CANFD_SendMsg(DevHandle, WriteCANIndex, CanMsg, (Int32)CanMsg.Length);
if (SendedNum >= 0)
{
Console.WriteLine("Success send frames:{0}", SendedNum);
}
else
{
Console.WriteLine("Send CAN data failed! {0}", SendedNum);
}
}
#region
// 添加取消标记源字段用于停止任务
private CancellationTokenSource CycleSendCts;
/// <summary>
/// 计算每毫秒对应的ticks数只需计算一次
/// </summary>
private double TicksPerMs;
// 类成员变量定义 精确记时用
private readonly Stopwatch Stopwatcher = new Stopwatch();
private long NextExecutionTime;
// 计算需要等待的时间
private long CurrentTime;
private long DelayTicks;
private int DelayMs;
private static readonly Random _random = new Random();
/// <summary>
/// 定时扫描更新数据 扫描Task
/// </summary>
private static Task CycleUpdateCmdTask { get; set; }
/// <summary>
/// 软件侧精确周期发送 CANFD 数据。
/// </summary>
/// <remarks>
/// - 使用 <see cref="Stopwatch"/> tick 作为时间基准,减少 DateTime 抖动;
/// - 使用 Task.Delay + SpinWait 组合等待;
/// - 使用 <see cref="CycleSendCts"/> 支持外部取消。
/// </remarks>
public void StartPrecisionCycleSendMsg()
{
// 创建取消标记源 用于控制任务的取消 允许在需要时通过取消令牌来优雅停止任务
var cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
// 保存取消标记,以便在停止时使用
CycleSendCts = cancellationTokenSource;//将取消标记源保存到类的成员变量CycleSendCts这样在外部调用停止方法时可以访问它
NextExecutionTime = 0;//初始化NextExecutionTime为0这个变量用于记录下一次执行的目标时间点
CycleSendTask = Task.Factory.StartNew(async () =>
{
try
{
// 设置当前线程为高优先级
Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
// 初始化完成后开始计时
Stopwatcher.Restart();
// 预先计算固定值
long CycleInTicks = (long)(SendCycle * TicksPerMs);
//临时测试用
//long lastTicks = Stopwatcher.ElapsedTicks;
//IsCycleSend
while (IsCycleSend && !token.IsCancellationRequested)
{
try
{
// 计算下一次执行时间点 将当前设置的发送周期SendCycle(毫秒)转换为Stopwatch的计时单位(tick)累加到NextExecutionTime上
NextExecutionTime += CycleInTicks; // 转换为Stopwatch计时单位
// 获取当前时间点以Stopwatch的tick为单位
CurrentTime = Stopwatcher.ElapsedTicks;
//计算需要等待的时间,即目标时间点(NextExecutionTime)与当前时间点(CurrentTime)的差值
DelayTicks = NextExecutionTime - CurrentTime;
// 如果还有等待时间,则等待,只有在目标时间点还未到达时才执行等待
if (DelayTicks > 0)
{
////此时是需要等待的,那么需要等待多久呢, 将需等待的tick数转换回毫秒
DelayMs = (int)(DelayTicks / TicksPerMs);
//20这个数据是预估和测试的可能跟Windows抖动误差就是20ms左右当然可以不用这个IF()判断直接SpinWait.SpinUntil(() => Stopwatcher.ElapsedTicks >= NextExecutionTime);但是会导致当前独占一个CPU核心线程
//所以设置一个20的阈值20ms以下的延迟使用SpinWait.SpinUntil进行自旋等待20ms以上的延迟使用Task.Delay进行异步等待让CPU不至于一直的独占
if (DelayMs <= 20)
{
SpinWait.SpinUntil(() => Stopwatcher.ElapsedTicks >= NextExecutionTime);
}
else
{
////使用Task.Delay进行异步等待大部分等待时间通过这种方式完成避免线程阻塞
await Task.Delay(DelayMs - 20, token);
//// 使用SpinWait.SpinUntil进行精确的微调等待。自旋等待会占用CPU资源但能提供更高的定时精度确保在精确的时间点执行
////上面的Task.Delay可能会因为系统调度等原因导致实际执行时间稍晚于预期因此在这里使用SpinWait.SpinUntil来确保在精确的时间点执行
SpinWait.SpinUntil(() => Stopwatcher.ElapsedTicks >= NextExecutionTime);
}
}
// 如果已经超过了计划时间,立即执行并重新校准
if (Stopwatcher.ElapsedTicks >= NextExecutionTime + CycleInTicks)
{
//检测是否发生了严重延迟(超过一个周期)。如果当前时间已经超过了下一次计划时间,则说明系统负载过高或其他原因导致无法按时执行,
//此时重置NextExecutionTime为当前时间避免连续的延迟累积
// 严重延迟,重新校准
NextExecutionTime = Stopwatcher.ElapsedTicks;
LoggerService.Info("定时发送延迟过大,重新校准时间");
Console.WriteLine("定时发送延迟过大,重新校准时间");
}
// 使用Stopwatch记录实际的执行间隔而不是DateTime
//Console.WriteLine($"--实际间隔(ms): {(Stopwatcher.ElapsedTicks - lastTicks) / TicksPerMs:F3}, 目标: {SendCycle}");
//lastTicks = Stopwatcher.ElapsedTicks;
//Console.WriteLine($"--当前时间(毫秒): {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}");
// 执行发送CAN逻辑
{
var GroupMsg = CmdData.GroupBy(x => x.MsgName);
USB2CANFD.CANFD_MSG[] CanMsg = new USB2CANFD.CANFD_MSG[GroupMsg.Count()];
for (int i = 0; i < GroupMsg.Count(); i++)
{
CanMsg[i] = new USB2CANFD.CANFD_MSG();
CanMsg[i].Data = new Byte[64];
}
// 发送构帧临时缓冲:每轮申请/释放,避免与其他线程共享同一指针导致并发问题
IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG)));
int Index = 0;
//循环给MSG赋值数据
foreach (var itemMsg in GroupMsg)
{
foreach (var itemSignal in itemMsg)
{
DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
}
DBCParserByFD.DBC_SyncValueToCANFDMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
CanMsg[Index] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CANFD.CANFD_MSG));
Index++;
}
//通过DBC写入数据后生成CanMsg
//将信号值填入CAN消息里面
// 释放非托管缓冲区(务必释放,否则长时间运行会造成内存泄漏)
Marshal.FreeHGlobal(msgPtSend);
//CanMsg[0].Flags = 5;
//发送CAN数据
int SendedNum = USB2CANFD.CANFD_SendMsg(DevHandle, WriteCANIndex, CanMsg, (Int32)CanMsg.Length);
if (SendedNum >= 0)
{
//Console.WriteLine("Success send frames:{0}", SendedNum);
IsSendOk = true;
}
else
{
//Console.WriteLine("Send CAN data failed! {0}", SendedNum);
IsSendOk = false;
}
}
}
catch (TaskCanceledException)
{
// 任务被取消,正常退出
IsSendOk = false;
break;
}
catch (Exception ex)
{
LoggerService.Error(ex.Message);
Console.WriteLine($"CAN周期发送异常: {ex.Message}");
// 短暂暂停避免异常情况下CPU占用过高
IsSendOk = false;
await Task.Delay(10, token);
}
}
IsSendOk = false;
}
catch (Exception ex)
{
// 确保在任何情况下(正常退出、异常、取消)都会停止计时器
Stopwatcher.Stop();
LoggerService.Error(ex.Message);
// 清理其他可能的资源
Console.WriteLine("CAN周期发送任务已结束资源已清理");
IsSendOk = false;
}
finally
{
// 确保在任何情况下(正常退出、异常、取消)都会停止计时器
Stopwatcher.Stop();
IsSendOk = false;
}
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
/// <summary>
/// 获取CAN消息数组中指定位置的数据
/// </summary>
/// <param name="sourceCanMsg">源CAN消息数组</param>
/// <param name="index">要获取的数据索引位置</param>
/// <returns>返回指定位置数据的新数组</returns>
private USB2CANFD.CANFD_MSG[] GetCanMsg(USB2CANFD.CANFD_MSG[] sourceCanMsg, int index)
{
// 参数检查
if (sourceCanMsg == null || sourceCanMsg.Length == 0 || index < 0 || index >= sourceCanMsg.Length)
{
return new USB2CANFD.CANFD_MSG[0];
}
// 创建单个元素的新数组
USB2CANFD.CANFD_MSG[] targetMsg = new USB2CANFD.CANFD_MSG[1];
targetMsg[0] = sourceCanMsg[index];
// 由于CANFD_MSG包含数组字段,需要手动初始化数据数组
targetMsg[0].Data = new byte[64];
// 复制数据
Array.Copy(sourceCanMsg[index].Data, targetMsg[0].Data, sourceCanMsg[index].Data.Length);
return targetMsg;
}
/// <summary>
/// 停止软件循环发送。
/// </summary>
/// <remarks>
/// - 置 <see cref="IsCycleSend"/> 为 false循环自然退出
/// - 触发 <see cref="CycleSendCts"/> 取消,用于尽快唤醒 Delay 并退出。
/// </remarks>
public void StopCycleSendMsg()
{
IsCycleSend = false;
CycleSendCts?.Cancel();
}
#endregion
#region 线
/// <summary>
/// 并行精确周期发送CAN数据
/// </summary>
public void StartParallelPrecisionCycleSendMsg()
{
// 创建取消标记源
var cancellationTokenSource = new CancellationTokenSource();
var token = cancellationTokenSource.Token;
// 保存取消标记,以便在停止时使用
CycleSendCts = cancellationTokenSource;
// 初始化计时基准
TicksPerMs = Stopwatch.Frequency / 1000.0;
// 分组消息,按照消息名称进行分组
var groupedMessages = CmdData.GroupBy(x => x.MsgName).ToList();
// 创建发送任务列表
var sendTasks = new List<Task>();
// 创建线程同步对象确保DBC操作和发送操作的线程安全
object dbcLock = new object();
// 为每组消息创建单独的发送任务
foreach (var messageGroup in groupedMessages)
{
var msgName = messageGroup.Key;
var signals = messageGroup.ToList();
// 为每个消息组创建一个发送任务
var sendTask = Task.Factory.StartNew(async () =>
{
try
{
// 设置线程优先级
Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
// 每个消息使用独立的计时器
var messageStopwatch = new Stopwatch();
messageStopwatch.Start();
// 计算该消息的发送周期(可以为不同消息设置不同周期)
// 这里可以从配置或参数中获取特定消息的周期
long cycleInTicks = (long)(SendCycle * TicksPerMs);
long nextExecutionTime = 0;
while (IsCycleSend && !token.IsCancellationRequested)
{
try
{
// 计算下一次执行时间
nextExecutionTime += cycleInTicks;
// 获取当前时间
long currentTime = messageStopwatch.ElapsedTicks;
long delayTicks = nextExecutionTime - currentTime;
// 精确等待逻辑
if (delayTicks > 0)
{
int delayMs = (int)(delayTicks / TicksPerMs);
if (delayMs <= 20)
{
SpinWait.SpinUntil(() => messageStopwatch.ElapsedTicks >= nextExecutionTime);
}
else
{
await Task.Delay(delayMs - 20, token);
SpinWait.SpinUntil(() => messageStopwatch.ElapsedTicks >= nextExecutionTime);
}
}
// 校准时间,处理严重延迟情况
if (messageStopwatch.ElapsedTicks >= nextExecutionTime + cycleInTicks)
{
nextExecutionTime = messageStopwatch.ElapsedTicks;
LoggerService.Info($"消息{msgName}定时发送延迟过大,重新校准时间");
}
// 使用锁确保DBC操作和发送的线程安全
lock (dbcLock)
{
// 构建CAN消息
IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)));
try
{
// 为信号赋值
foreach (var signal in signals)
{
DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(msgName),
new StringBuilder(signal.SignalName), signal.SignalCmdValue);
}
// 同步值到CAN消息
DBCParserByFD.DBC_SyncValueToCANFDMsg(DBCHandle, new StringBuilder(msgName), msgPtSend);
var canMsg = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CAN.CAN_MSG));
// 直接发送此消息,无需通过队列
USB2CAN.CAN_MSG[] canMsgArray = new USB2CAN.CAN_MSG[] { canMsg };
int sendedNum = USB2CAN.CAN_SendMsg(DevHandle, WriteCANIndex, canMsgArray, 1);
// 更新发送状态
if (sendedNum >= 0)
{
// 发送成功
IsSendOk = true;
}
else
{
IsSendOk = false;
LoggerService.Info($"消息{msgName}发送失败: {sendedNum}");
}
}
finally
{
// 确保释放非托管资源
Marshal.FreeHGlobal(msgPtSend);
}
}
}
catch (TaskCanceledException)
{
LoggerService.Info($"消息{msgName}发送任务被取消");
break;
}
catch (Exception ex)
{
LoggerService.Info($"消息{msgName}发送异常: {ex.Message}");
await Task.Delay(10, token);
}
}
}
catch (Exception ex)
{
LoggerService.Info($"消息{msgName}发送线程异常: {ex.Message}");
}
finally
{
LoggerService.Info($"消息{msgName}发送线程已退出");
}
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
sendTasks.Add(sendTask);
}
// 创建监控任务,监控所有发送任务的状态
CycleSendTask = Task.Factory.StartNew(async () =>
{
try
{
// 等待所有任务完成或取消
await Task.WhenAll(sendTasks.ToArray());
}
catch (Exception ex)
{
LoggerService.Info($"并行发送监控任务异常: {ex.Message}");
}
finally
{
LoggerService.Info("并行发送任务已全部结束");
IsSendOk = false;
}
}, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
#endregion
#region
private bool _SchEnable;
/// <summary>
/// 调度表使能开关。
/// </summary>
/// <remarks>
/// 该开关用于控制“硬件调度表发送”及“CmdData 变化时是否增量更新调度表”。
/// </remarks>
public bool SchEnable
{
get { return _SchEnable; }
set
{
_SchEnable = value;
RaisePropertyChanged();
}
}
/// <summary>
/// 调度表的发送的报文数据
/// </summary>
private USB2CANFD.CANFD_MSG[] SchCanMsg { get; set; }
/// <summary>
/// 依据消息的分组
/// </summary>
private IEnumerable<IGrouping<string, CanCmdData>> GroupMsg { get; set; }
/// <summary>
/// 调度表集合数据
/// 总共3个调度表第一个表里面包含3帧数据第二个调度表包含6帧数据第三个调度表包含11帧数据
/// Byte[] MsgTabNum = new Byte[3] { 3, 6, 11 };
/// </summary>
private Byte[] MsgTabNum { get; set; }
/// <summary>
/// 调度表发送的次数集合
/// 第一个调度表循环发送数据第二个调度表循环发送数据第三个调度表只发送3次
/// UInt16[] SendTimes = new UInt16[3] { 0xFFFF, 0xFFFF, 3 };
/// </summary>
private UInt16[] SendTimes { get; set; }
/// <summary>
/// 预设的调度表的个数 常值
/// </summary>
private const int MsgTabCount = 5;
/// <summary>
/// 定时更新时间
/// </summary>
private int UpdateCycle { get; set; } = 100;
/// <summary>
/// CNA 调度表的配置信息
/// </summary>
public List<CANFdScheduleConfigDto> ListCANScheduleConfig { get; set; }
Random random = new Random();
/// <summary>
/// 监控数据
/// 查找问题用,平时不用
/// </summary>
//public MonitorValueLog monitorValueLog { get; set; }
/// <summary>
/// 更新数据 测试用废弃了
/// </summary>
public void UpdateValue()
{
//通过DBC进行对消息赋值
IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG)));
int Index = 0;
//循环给MSG赋值数据
foreach (var itemMsg in GroupMsg)
{
foreach (var itemSignal in itemMsg)
{
itemSignal.SignalCmdValue = random.Next(0, 100);
DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
}
DBCParserByFD.DBC_SyncValueToCANFDMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
SchCanMsg[Index] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CANFD.CANFD_MSG));
Index++;
}
//通过DBC写入数据后生成CanMsg
//将信号值填入CAN消息里面
//释放申请的临时缓冲区
Marshal.FreeHGlobal(msgPtSend);
////总共3个调度表第一个表里面包含3帧数据第二个调度表包含6帧数据第三个调度表包含11帧数据
//MsgTabNum = new Byte[1] { 1 };
////第一个调度表循环发送数据第二个调度表循环发送数据第三个调度表只发送3次
//SendTimes = new UInt16[1] { 0xFFFF };
//var ret = USB2CANFD.CAN_SetSchedule(DevHandle, WriteCANIndex, SchCanMsg, MsgTabNum, SendTimes, 1);//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可
var ret = USB2CANFD.CANFD_UpdateSchedule(DevHandle, WriteCANIndex, 0, 0, SchCanMsg, 1);//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可
if (ret == USB2CANFD.CANFD_SUCCESS)
{
Console.WriteLine("Update CAN Schedule Success");
}
else
{
Console.WriteLine("Update CAN Schedule Error ret = {0}", ret);
return;
}
}
/// <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;
//依据报文进行分组
GroupMsg = CmdData.GroupBy(x => x.MsgName)!;
//初始化调度表要发送的消息结构
SchCanMsg = new USB2CANFD.CANFD_MSG[GroupMsg.Count()];
for (int i = 0; i < GroupMsg.Count(); i++)
{
SchCanMsg[i] = new USB2CANFD.CANFD_MSG();
SchCanMsg[i].Data = new Byte[64];
}
//通过DBC进行对消息赋值
IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG)));
int Index = 0;
//循环给MSG赋值数据
foreach (var itemMsg in GroupMsg)
{
foreach (var itemSignal in itemMsg)
{
DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
}
DBCParserByFD.DBC_SyncValueToCANFDMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
//每个分组就是一个帧指令/消息数据
SchCanMsg[Index] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CANFD.CANFD_MSG));
// 将“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++;
}
//通过DBC写入数据后生成CanMsg
//将信号值填入CAN消息里面
//释放申请的临时缓冲区
Marshal.FreeHGlobal(msgPtSend);
//********就是可以设置多个调度表放那里,但是运行时同一个时刻只能运行调度表其中的一个 ********
//****** 控制报文SchCanMsg和调度器中第一个调度器中的报文集合和要更新的报文集合都是同一个变量SchCanMsg ********
// *** SchCanMsg的Index序号和ListCANScheduleConfig的MsgIndex是一样的 ***
//图莫斯的Sample总共3个调度表第一个表里面包含3帧数据第二个调度表包含6帧数据第三个调度表包含11帧数据
//预设5个调度表但是我们只用其中第一个调度表第一个调度表中包括多少消息帧由系统的控制指令的帧的分布决定SchCanMsg.Count()是所需要的控制发送的帧,都放到第一个调度表中
MsgTabNum = new Byte[MsgTabCount] { (byte)SchCanMsg.Count(), 1, 1, 1, 1 };
//0xFFFF调度表循环发送数据X调度表循环发送的次数
//设置每个调度表的发送方式,约定全部为循环发送
SendTimes = new UInt16[MsgTabCount] { 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF };
//SchCanMsg:需要发送的消息集合MsgTabNum调度表集合数据SendTimes发送次数集合数据调度表个数MsgTabCount
var ret = USB2CANFD.CANFD_SetSchedule(DevHandle, WriteCANIndex, SchCanMsg, MsgTabNum, SendTimes, MsgTabCount);//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可
if (ret == USB2CANFD.CANFD_SUCCESS)
{
Console.WriteLine("Set CAN Schedule Success");
}
else
{
Console.WriteLine("Set CAN Schedule Error ret = {0}", ret);
LoggerService.Info($"Set CAN Schedule Error; 返回错误代码:{ret}");
return;
}
// 约定使用调度器索引 0同一时刻只能运行一个调度器且本项目把所有控制帧都集中在同一个调度器里
//CAN_MSG的TimeStamp就是这个报文发送的周期由调度器协调
ret = USB2CANFD.CANFD_StartSchedule(DevHandle, WriteCANIndex, (byte)0, (byte)100, (byte)ListCANScheduleConfig.FirstOrDefault()!.OrderSend);
if (ret == USB2CANFD.CANFD_SUCCESS)
{
Console.WriteLine($"Start CAN Schedule 1 SuccessSchTabIndex{(byte)0} - Cycle{(byte)100} - OrderSend{(byte)1}");
}
else
{
Console.WriteLine("Start CAN Schedule 1 Error ret = {0}", ret);
LoggerService.Info($"Start CAN Schedule 1 Error;");
return;
}
//foreach (var itemGroupMsg in GroupMsg)
//{
// if (itemGroupMsg == null) continue;
// if (ListCANScheduleConfig.Any(a => a.MsgName!.Contains(itemGroupMsg.Key)))
// {
// var CANScheduleConfig = ListCANScheduleConfig.FindFirst(a => a.MsgName!.Contains(itemGroupMsg.Key));
// //配置表里面包括这个报文消息内容
// ret = USB2CANFD.CAN_StartSchedule(DevHandle, WriteCANIndex, (byte)CANScheduleConfig.SchTabIndex, (byte)CANScheduleConfig.Cycle, (byte)CANScheduleConfig.OrderSend);
// if (ret == USB2CANFD.CAN_SUCCESS)
// {
// Console.WriteLine($"Start CAN Schedule 1 SuccessSchTabIndex{(byte)CANScheduleConfig.SchTabIndex} - Cycle{(byte)CANScheduleConfig.Cycle} - OrderSend{(byte)CANScheduleConfig.OrderSend}");
// }
// else
// {
// Console.WriteLine("Start CAN Schedule 1 Error ret = {0}", ret);
// LoggerService.Info($"Start CAN Schedule 1 Error;消息名称:{CANScheduleConfig.MsgName}");
// return;
// }
// }
// else
// {
// LoggerService.Info($"调度表配置未发现对应的消息报文信息;报文信息{itemGroupMsg.Key}");
// }
//}
//走到这里说明调度表执行的是OK的
//IsSendOk = true;
}
/// <summary>
/// 停止硬件调度表。
/// </summary>
/// <remarks>
/// 停止后设备侧将不再按周期自动发送。
/// </remarks>
public void StopSchedule()
{
ret = USB2CANFD.CANFD_StopSchedule(DevHandle, WriteCANIndex);//启动第一个调度表,表里面的CAN帧并行发送
if (ret == USB2CANFD.CANFD_SUCCESS)
{
IsSendOk = false;
Console.WriteLine("Stop CAN Schedule");
LoggerService.Info($"Stop CAN Schedule");
}
else
{
Console.WriteLine("Start CAN Schedule Error ret = {0}", ret);
LoggerService.Info($"Stop CAN Schedule");
return;
}
}
/// <summary>
/// 循环使用的
/// </summary>
private int CycleUpdateIndex = 0;
/// <summary>
/// 绑定/刷新驱动侧要发送的 CmdData并自动维护“值变化事件”的订阅。
/// </summary>
/// <param name="cmdData">新的指令集合。</param>
/// <remarks>
/// 注意:必须先对旧集合取消订阅,否则会造成重复触发和内存泄漏。
/// </remarks>
public void LoadCmdDataToDrive(List<CanCmdData> cmdData)
{
// Unsubscribe from events on the old CmdData items
if (CmdData != null && CmdData.Count > 0)
{
foreach (var cmd in CmdData)
{
cmd.CanCmdDataChangedHandler -= CmdData_CanCmdDataChangedHandler;
}
}
// Set the new data and subscribe to events
CmdData = cmdData;
foreach (var cmd in cmdData)
{
cmd.CanCmdDataChangedHandler += CmdData_CanCmdDataChangedHandler;
}
}
/// <summary>
/// CmdData 中任意信号值变化事件处理。
/// </summary>
/// <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>
/// <remarks>
/// 触发条件:<see cref="IsCycleSend"/> 与 <see cref="SchEnable"/> 同时为 true。
/// 当前实现为“全量覆盖更新”CANFD_UpdateSchedule
/// </remarks>
public void UpdateSchDataByCmdDataChanged()
{
try
{
if (!IsCycleSend) return;
if (!SchEnable) return;
// 基础防御:确保 DBC/ 调度表 / 分组已经初始化
if (DBCHandle == 0 || SchCanMsg == null || GroupMsg == null)
{
return;
}
lock (SchUpdateLock)
{
// 重新构建每一帧的 Data并覆盖更新到设备调度表
IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG)));
int CycleUpdateIndex = 0;
//循环给MSG赋值数据顺序是固定的跟初始时设置是一样的
foreach (var itemMsg in GroupMsg)
{
foreach (var itemSignal in itemMsg)
{
//itemSignal.SignalCmdValue = random.Next(0, 100); //仿真测试数据使用
var SetSignalValue = DBCParserByFD.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
//monitorValueLog.UpdateValue1(SetSignalValue);
}
var SyncValueToCanMsg = DBCParserByFD.DBC_SyncValueToCANFDMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend);
//monitorValueLog.UpdateValue2(SyncValueToCanMsg);
SchCanMsg[CycleUpdateIndex] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CANFD.CANFD_MSG));
CycleUpdateIndex++;
}
//通过DBC写入数据后生成CanMsg
//将信号值填入CAN消息里面
// 释放非托管缓冲
Marshal.FreeHGlobal(msgPtSend);
//CAN_UpdateSchedule 官网解释
// ---MsgTabIndex CAN调度表索引号
// ---MsgIndex 开始更新帧起始索引,若起始索引大于调度表帧数,则将帧添加到调度表后面
// ---pCanMsg 需要更新的CAN帧指针
// ---MsgNum pCanMsgTab里面包含的有效帧数
//CAN_UpdateSchedule中的MsgIndex表示当前的调度器中的帧Index序号
//因为调度表中的帧集合和控制帧的集合和要更新的帧集合都是同一个集合SchCanMsg
// 默认调度表索引 0一次性覆盖更新所有帧MsgIndex=0, MsgNum=帧数)
var ret = USB2CANFD.CANFD_UpdateSchedule(DevHandle, WriteCANIndex, (byte)0, (byte)(0), SchCanMsg, (byte)SchCanMsg.Count());//配置调度表,该函数耗时可能会比较长,但是只需要执行一次即可
if (ret == USB2CANFD.CANFD_SUCCESS)
{
IsSendOk = true;
Console.WriteLine($"Update CAN Schedule Success -- SchTabIndex{(byte)0} -- MsgIndex{(byte)(0)}-- SchCanMsg.Count(){(SchCanMsg.Count())} ");
//monitorValueLog.UpdateValue3(ret);
}
else
{
IsSendOk = false;
//Console.WriteLine($"Update CAN Schedule Error ret = {ret} -- SchTabIndex{(byte)0} -- MsgIndex{(byte)(0)}");
//monitorValueLog.UpdateValue3(ret);
LoggerService.Info($"更新调度表失败,错误码:{ret}");
//return;
}
}
}
catch (Exception ex)
{
IsSendOk = false;
LoggerService.Info($"时间:{DateTime.Now.ToString()}-【MSG】-{ex.Message}");
}
}
#endregion
private bool _IsCycleRevice;
/// <summary>
/// 是否循环接收数据。
/// </summary>
public bool IsCycleRevice
{
get { return _IsCycleRevice; }
set { _IsCycleRevice = value; RaisePropertyChanged(); }
}
private bool _IsCycleSend;
/// <summary>
/// 是否循环发送数据。
/// </summary>
public bool IsCycleSend
{
get { return _IsCycleSend; }
set { _IsCycleSend = value; RaisePropertyChanged(); }
}
/// <summary>
/// 软件循环发送周期(毫秒)。
/// </summary>
public ushort SendCycle { get; set; } = 200;
/// <summary>
/// 接收轮询周期(毫秒)。
/// </summary>
public ushort ReviceCycle { get; set; } = 200;
/// <summary>
/// CycleRevice 扫描Task
/// </summary>
private static Task CycleReviceTask { get; set; }
/// <summary>
/// CycleSend 扫描Task
/// </summary>
private static Task CycleSendTask { get; set; }
StringBuilder ValueSb = new StringBuilder(16);
double[] ValueDouble = new double[5];
private bool _IsSendOk;
/// <summary>
/// 最近一次发送是否成功。
/// </summary>
public bool IsSendOk
{
get { return _IsSendOk; }
set
{
if (_IsSendOk != value)
{
RaisePropertyChanged();
_IsSendOk = value;
}
}
}
private bool _IsReviceOk;
/// <summary>
/// 最近一次接收是否成功。
/// </summary>
public bool IsReviceOk
{
get { return _IsReviceOk; }
set
{
if (_IsReviceOk != value)
{
RaisePropertyChanged();
_IsReviceOk = value;
}
}
}
/// <summary>
/// 当前激活的指令集合(由 Service/UI 下发)。
/// </summary>
public List<CanCmdData> CmdData { get; set; } = new List<CanCmdData>();
/// <summary>
/// 启动后台循环接收 CANFD 报文,并同步到 DBC 信号实时值。
/// </summary>
/// <remarks>
/// 注意:当前实现每轮接收都会分配 msgPtRead 并释放,长时间运行会造成 GC 压力和潜在碎片;
/// 若后续需要优化,可参考 CAN 版本的“接收缓冲池复用 + CloseDevice 互斥释放”。
/// </remarks>
public void StartCycleReviceCanMsg()
{
CycleReviceTask = Task.Run(async () =>
{
//var ret = USB2CANFDFD.CANFD_StartGetMsg(DevHandle, ReadCANIndex);
while (IsCycleRevice)
{
await Task.Delay(ReviceCycle);
try
{
//另外一个CAN通道读取数据
USB2CANFD.CANFD_MSG[] CanMsgBuffer = new USB2CANFD.CANFD_MSG[128];
IntPtr msgPtRead = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG)) * CanMsgBuffer.Length);
int CanNum = USB2CANFD.CANFD_GetMsg(DevHandle, ReadCANIndex, msgPtRead, CanMsgBuffer.Length);
if (CanNum > 0)
{
IsReviceOk = true;
Console.WriteLine("Read CanMsgNum = {0}", CanNum);
for (int i = 0; i < CanNum; i++)
{
CanMsgBuffer[i] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure((IntPtr)(msgPtRead + i * Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG))), typeof(USB2CANFD.CANFD_MSG));
Console.WriteLine("CanMsg[{0}].ID = 0x{1}", i, CanMsgBuffer[i].ID.ToString("X8"));
//Console.WriteLine("CanMsg[{0}].TimeStamp = {1}",i,CanMsgBuffer[i].TimeStamp);
Console.Write("CanMsg[{0}].Data = ", i);
for (int j = 0; j < CanMsgBuffer[i].DLC; j++)
{
Console.Write("{0} ", CanMsgBuffer[i].Data[j].ToString("X2"));
}
Console.WriteLine("");
//报文给高速记录的服务
HighSpeedDataService.AppendOrUpdateMsg(new Models.HighSpeed.CommMsg()
{
Category = "CANFD",
MsgInfo = "0x" + CanMsgBuffer[i].ID.ToString("X8"),
MsgData = BitConverter.ToString(CanMsgBuffer[i].Data),
Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
});
}
}
else if (CanNum == 0)
{
IsReviceOk = false;
//Console.WriteLine("No CAN data!");
}
else
{
IsReviceOk = false;
//Console.WriteLine("Get CAN data error!");
}
//Console.WriteLine("");
//将CAN消息数据填充到信号里面
DBCParserByFD.DBC_SyncCANFDMsgToValue(DBCHandle, msgPtRead, CanNum);
//循环获取消息的数据
foreach (var item in ListCanFdDbcModel)
{
//有配置的名称的,认为是有用的,则需要读取数据
//if (!string.IsNullOrEmpty(item.Name))
//{
//DBCParserByFD.DBC_GetSignalValueStr(DBCHandle, new StringBuilder(item.MsgName), new StringBuilder(item.SignalName), ValueSb);
//double[] ValueDouble;
DBCParserByFD.DBC_GetSignalValue(DBCHandle, new StringBuilder(item.MsgName), new StringBuilder(item.SignalName), ValueDouble);
//item.SignalRtValueSb = ValueSb;
item.SignalRtValue = ValueDouble[0].ToString();
//Console.Write(ValueSb.ToString());
//}
}
// 释放数据缓冲区,必须释放,否则程序运行一段时间后会报内存不足
Marshal.FreeHGlobal(msgPtRead);
Thread.Sleep(10);
////获取信号值并打印出来
//StringBuilder ValueStr = new StringBuilder(32);
//DBCParserByFD.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("TX1"), new StringBuilder("COM_current_Power"), ValueStr);
//Console.WriteLine("COM_current_Power = {0}", ValueStr);
//DBCParserByFD.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("TX1"), new StringBuilder("COM_Curr_dc"), ValueStr);
//Console.WriteLine("COM_Curr_dc = {0}", ValueStr);
}
catch (Exception ex)
{
IsReviceOk = false;
LoggerService.Error(ex.Message);
//LoggerService.Info($"时间:{DateTime.Now.ToString()}-【Meter】-{ex.Message}");
}
}
IsReviceOk = false;
});
}
/// <summary>
/// 接受CAN消息
/// </summary>
public void ReciveCanMsg()
{
//另外一个CAN通道读取数据
USB2CANFD.CANFD_MSG[] CanMsgBuffer = new USB2CANFD.CANFD_MSG[10];
msgPt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG)) * CanMsgBuffer.Length);
int CanNum = USB2CANFD.CANFD_GetMsg(DevHandle, ReadCANIndex, msgPt, CanMsgBuffer.Length);
if (CanNum > 0)
{
Console.WriteLine("Read CanMsgNum = {0}", CanNum);
for (int i = 0; i < CanNum; i++)
{
CanMsgBuffer[i] = (USB2CANFD.CANFD_MSG)Marshal.PtrToStructure((IntPtr)(msgPt + i * Marshal.SizeOf(typeof(USB2CANFD.CANFD_MSG))), typeof(USB2CANFD.CANFD_MSG));
Console.WriteLine("CanMsg[{0}].ID = 0x{1}", i, CanMsgBuffer[i].ID.ToString("X8"));
//Console.WriteLine("CanMsg[{0}].TimeStamp = {1}",i,CanMsgBuffer[i].TimeStamp);
Console.Write("CanMsg[{0}].Data = ", i);
for (int j = 0; j < CanMsgBuffer[i].DLC; j++)
{
Console.Write("{0} ", CanMsgBuffer[i].Data[j].ToString("X2"));
}
Console.WriteLine("");
}
}
else if (CanNum == 0)
{
Console.WriteLine("No CAN data!");
}
else
{
Console.WriteLine("Get CAN data error!");
}
Console.WriteLine("");
//将CAN消息数据填充到信号里面
DBCParserByFD.DBC_SyncCANFDMsgToValue(DBCHandle, msgPt, CanNum);
//获取信号值并打印出来
StringBuilder ValueStr = new StringBuilder(32);
DBCParserByFD.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_moto_speed"), new StringBuilder("moto_speed"), ValueStr);
Console.WriteLine("moto_speed = {0}", ValueStr);
DBCParserByFD.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_oil_pressure"), new StringBuilder("oil_pressure"), ValueStr);
Console.WriteLine("oil_pressure = {0}", ValueStr);
DBCParserByFD.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_speed_can"), new StringBuilder("speed_can"), ValueStr);
Console.WriteLine("speed_can = {0}", ValueStr);
}
/// <summary>
/// 关闭设备并释放资源。
/// </summary>
/// <remarks>
/// 释放顺序要点:
/// - 关闭设备并下置状态位;
/// - 若启用调度表则先停止;
/// - 等待接收任务退出(短等待,避免 UI 卡死)。
/// </remarks>
public void CloseDevice()
{
//关闭设备
USB_DEVICEByFD.USB_CloseDevice(DevHandle);
OpenState = false;
DbcParserState = false;
IsCycleRevice = false;
IsCycleSend = false;
if (SchEnable)
{
StopSchedule();
}
// 确保定时器发送被停止并释放资源
//try { StopTimerCycleSendMsg(); } catch { }
// 等待接收任务结束后释放非托管缓冲区,避免并发释放
try
{
var task = CycleReviceTask;
if (task != null && !task.IsCompleted)
{
task.Wait(TimeSpan.FromMilliseconds(ReviceCycle + 500));
}
}
catch { }
}
}
}