using CapMachine.Wpf.Models.Tag; using CapMachine.Wpf.Services; using HslCommunication; using NPOI.OpenXmlFormats.Wordprocessing; using Prism.Ioc; using Prism.Mvvm; using System; 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; using System.Threading.Tasks; using System.Windows.Forms; using System.Windows.Interop; using static CapMachine.Wpf.CanDrive.USB2CAN; namespace CapMachine.Wpf.CanDrive { /// /// Toomoss CAN /// public class ToomossCan : BindableBase { private readonly IContainerProvider ContainerProvider; private readonly object _dbcParserLock = new object(); private readonly object _canSendLock = new object(); private readonly object _recvBufferLock = new object(); private const int DbcStringBufferCapacity = 256; private const int RecvBufferSize = 128; /// /// 预分配的接收缓冲区指针,避免每次循环分配/释放 /// private IntPtr RecvMsgBufferPtr = IntPtr.Zero; /// /// 接收缓冲区对应的托管数组 /// private readonly USB2CAN.CAN_MSG[] RecvMsgBuffer = new USB2CAN.CAN_MSG[RecvBufferSize]; /// /// 实例化函数 /// public ToomossCan(IContainerProvider containerProvider) { ContainerProvider = containerProvider; HighSpeedDataService = ContainerProvider.Resolve(); //Stopwatch.Frequency表示高精度计时器每秒的计数次数(ticks/秒)每毫秒的ticks数 = 每秒的ticks数 ÷ 1000 TicksPerMs = Stopwatch.Frequency / 1000.0; } /// /// 开始CAN的驱动 /// public void StartCanDrive() { IsExistsDllFile(); ScanDevice(); OpenDevice(); GetDeviceInfo(); GetCANConfig(); InitCAN(); } /// /// HighSpeedDataService 实例 /// public HighSpeedDataService HighSpeedDataService { get; set; } /// /// 开始Dbc文件写入 /// public ObservableCollection StartDbc(string DbcPath) { DBC_Parser(DbcPath); return ListCanDbcModel; } ///// ///// 获取Can DBC数据集合 ///// //public void LoadCanDbcData(ObservableCollection canDbcModels) //{ // ListCanDbcModel = canDbcModels; //} /// /// Dbc消息集合 /// 包括读取的实时值和数据 /// public ObservableCollection ListCanDbcModel { get; set; } = new ObservableCollection(); #region 公共变量 /// /// 设备固件信息 /// public USB_DEVICE.DEVICE_INFO DevInfo = new USB_DEVICE.DEVICE_INFO(); /// /// CAN Config /// USB2CAN.CAN_INIT_CONFIG CANConfig = new USB2CAN.CAN_INIT_CONFIG(); /// /// DBC加载后获取的Handle /// DBC Handle /// public UInt64 DBCHandle { get; set; } /// /// 扫描设备Handle集合 /// public Int32[] DevHandles { get; set; } = new Int32[20]; /// /// 扫描设备Handle /// public Int32 DevHandle { get; set; } = 0; /// /// Write CAN Index /// 通道的含义 /// 描述: /// 读取接收到的CAN消息,推荐使用该函数。 ///原型: ///int WINAPI CAN_GetMsgWithSize(int DevHandle, unsigned char CANIndex, CAN_MSG* pCanGetMsg, int BufferSize); ///参数: ///DevHandle 设备句柄,本质为设备序号的低4字节,可以通过调用USB_ScanDevice函数获得。 ///CANIndex CAN通道索引号,0-对应CAN1,1-对应CAN2。 ///pCanGetMsg 存储CAN消息缓冲区首地址。 ///BufferSize 存储CAN消息缓冲区大小。 ///返回值: //大于等于0表示从CAN适配器内部成功读取到的CAN消息帧数,若返回值小于0则说明调用该函数失败。 /// public Byte WriteCANIndex { get; set; } = 0; /// /// Read CAN Index /// 通道的含义 /// 描述: ///读取接收到的CAN消息,推荐使用该函数。 ///原型: ///int WINAPI CAN_GetMsgWithSize(int DevHandle, unsigned char CANIndex, CAN_MSG* pCanGetMsg, int BufferSize); ///参数: ///DevHandle 设备句柄,本质为设备序号的低4字节,可以通过调用USB_ScanDevice函数获得。 ///CANIndex CAN通道索引号,0-对应CAN1,1-对应CAN2。 ///pCanGetMsg 存储CAN消息缓冲区首地址。 ///BufferSize 存储CAN消息缓冲区大小。 ///返回值: ///大于等于0表示从CAN适配器内部成功读取到的CAN消息帧数,若返回值小于0则说明调用该函数失败。 /// public Byte ReadCANIndex { get; set; } = 0; private bool _OpenState; /// /// 打开设备的状态 /// public bool OpenState { get { return _OpenState; } set { _OpenState = value; RaisePropertyChanged(); } } private bool _DbcParserState; /// /// DBC解析的状态 /// public bool DbcParserState { get { return _DbcParserState; } set { _DbcParserState = value; RaisePropertyChanged(); } } /// /// 扫描到设备个数 /// public Int32 DevNum { get; set; } public Int32 ret { get; set; } public string dllFilePath { get; set; } = "USB2XXX.dll"; /// /// 消息值Pt /// public IntPtr msgPt { get; set; } #endregion /// /// ******************【1】********************* /// 是否存在Dll文件 /// /// 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; } /// /// ******************【2】********************* /// 扫描查找设备,并将每个设备的唯一设备号存放到数组中,后面的函数需要用到 /// /// public bool ScanDevice() { DevNum = USB_DEVICE.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; } } /// /// ******************【3】********************* /// 打开设备 /// /// public bool OpenDevice() { //打开设备 OpenState = USB_DEVICE.USB_OpenDevice(DevHandle); if (!OpenState) { Console.WriteLine("Open device error!"); return false; } else { Console.WriteLine("Open device success!"); return true; } } /// /// ******************【4】********************* /// 获取设备的固件信息 /// /// public bool GetDeviceInfo() { //获取固件信息 StringBuilder FuncStr = new StringBuilder(256); OpenState = USB_DEVICE.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(" Functions String:" + FuncStr); StringBuilder DLLBuildDate = new StringBuilder(256); USB_DEVICE.DEV_GetDllBuildTime(DLLBuildDate); Console.WriteLine(" DLL Build Date:" + DLLBuildDate); return true; } } /// /// ******************【5】********************* /// 获取设备Config配置 /// public void GetCANConfig() { CANConfig.CAN_Mode = 0x80;//正常模式并接入终端电阻 //获取CAN波特率参数 ret = USB2CAN.CAN_GetCANSpeedArg(DevHandle, ref CANConfig, 500000); if (ret != USB2CAN.CAN_SUCCESS) { Console.WriteLine("Get CAN Speed failed!"); return; } else { Console.WriteLine("Get CAN Speed Success!"); } } /// /// ******************【6】********************* /// 初始化CAN /// public void InitCAN() { //初始化CAN ret = USB2CAN.CAN_Init(DevHandle, WriteCANIndex, ref CANConfig); if (ret != USB2CAN.CAN_SUCCESS) { Console.WriteLine("Config CAN failed!"); return; } else { Console.WriteLine("WriteCANIndex Config CAN Success!"); } ret = USB2CAN.CAN_Init(DevHandle, ReadCANIndex, ref CANConfig); if (ret != USB2CAN.CAN_SUCCESS) { Console.WriteLine("Config CAN failed!"); return; } else { Console.WriteLine("ReadCANIndex Config CAN Success!"); } Console.WriteLine(""); } /// /// ******************【7】********************* /// DBC解析 /// public void DBC_Parser(string Path) { lock (_dbcParserLock) { //解析DBC文件 DBCHandle = CAN_DBCParser.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!"); } ListCanDbcModel.Clear(); //打印DBC里面报文和信号相关信息 int DBCMsgNum = CAN_DBCParser.DBC_GetMsgQuantity(DBCHandle); for (int i = 0; i < DBCMsgNum; i++) { StringBuilder MsgName = new StringBuilder(DbcStringBufferCapacity); CAN_DBCParser.DBC_GetMsgName(DBCHandle, i, MsgName); //Console.WriteLine("Msg.Name = {0}", MsgName); int DBCSigNum = CAN_DBCParser.DBC_GetMsgSignalQuantity(DBCHandle, MsgName); StringBuilder Publisher = new StringBuilder(DbcStringBufferCapacity); CAN_DBCParser.DBC_GetMsgPublisher(DBCHandle, MsgName, Publisher); long MsgId; MsgId = CAN_DBCParser.DBC_GetMsgIDByName(DBCHandle, MsgName); //Console.Write("Signals:"); for (int j = 0; j < DBCSigNum; j++) { StringBuilder SigName = new StringBuilder(DbcStringBufferCapacity); CAN_DBCParser.DBC_GetMsgSignalName(DBCHandle, MsgName, j, SigName); //Console.Write("{0} ", SigName); //增加信息数据 ListCanDbcModel.Add(new CanDbcModel() { MsgName = MsgName.ToString(), MsgId = "0x" + MsgId.ToString("X8"), SignalName = SigName.ToString(), SignalDesc = "", SignalUnit = "", SignalRtValue = "", Publisher = Publisher.ToString() }); } //Console.WriteLine(""); } //Dbc解析成功 DbcParserState = true; } } /// /// 发送CAN数据 /// 发送一次 /// public void SendCanMsg(List CmdData) { SendCanMessagesByDbc(CmdData); } private void SendCanMessagesByDbc(IEnumerable sourceCmdData) { var cmdDataSnapshot = sourceCmdData .Where(x => !string.IsNullOrWhiteSpace(x.MsgName) && !string.IsNullOrWhiteSpace(x.SignalName)) .Select(x => new CanCmdData { ConfigName = x.ConfigName, MsgName = x.MsgName, SignalName = x.SignalName, SignalCmdValue = x.SignalCmdValue }) .ToList(); if (cmdDataSnapshot.Count == 0) { return; } var groupMsg = cmdDataSnapshot.GroupBy(x => x.MsgName).ToList(); int msgCount = groupMsg.Count; USB2CAN.CAN_MSG[] canMsgBuffer = new USB2CAN.CAN_MSG[msgCount]; for (int i = 0; i < canMsgBuffer.Length; i++) { canMsgBuffer[i] = new USB2CAN.CAN_MSG(); canMsgBuffer[i].Data = new byte[8]; } IntPtr msgPt = IntPtr.Zero; try { msgPt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG))); int index = 0; lock (_canSendLock) { lock (_dbcParserLock) { //循环给MSG赋值数据 foreach (var itemMsg in groupMsg) { foreach (var itemSignal in itemMsg) { int setResult = CAN_DBCParser.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue); if (setResult != CAN_DBCParser.DBC_PARSER_OK) { return; } } int syncResult = CAN_DBCParser.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPt); if (syncResult != CAN_DBCParser.DBC_PARSER_OK) { return; } canMsgBuffer[index] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(msgPt, typeof(USB2CAN.CAN_MSG)); if (canMsgBuffer[index].Data == null || canMsgBuffer[index].Data.Length != 8) { canMsgBuffer[index].Data = new byte[8]; } index++; } } USB2CAN.CAN_SendMsg(DevHandle, WriteCANIndex, canMsgBuffer, (uint)msgCount); } } finally { if (msgPt != IntPtr.Zero) { Marshal.FreeHGlobal(msgPt); } } } private bool _IsCycleRevice; /// /// 是否循环接收数据 /// public bool IsCycleRevice { get { return _IsCycleRevice; } set { _IsCycleRevice = value; RaisePropertyChanged(); } } private bool _IsCycleSend; /// /// 是否循环发送数据 /// public bool IsCycleSend { get { return _IsCycleSend; } set { _IsCycleSend = value; RaisePropertyChanged(); } } /// /// 循环发送数据 /// public ushort SendCycle { get; set; } = 100; /// /// 循环接受数据 /// public ushort ReviceCycle { get; set; } = 500; /// /// CycleRevice 扫描Task /// private Task? CycleReviceTask { get; set; } /// /// CycleSend 扫描Task /// private Task? CycleSendTask { get; set; } StringBuilder ValueSb = new StringBuilder(16); /// /// 要发送的数据 /// public List CmdData { get; set; } = new List(); /// /// 循环发送数据 /// public void StartCycleSendMsg() { if (CycleSendTask != null && !CycleSendTask.IsCompleted) { return; } CycleSendTask = Task.Run(async () => { while (IsCycleSend) { await Task.Delay(SendCycle); try { SendCanMessagesByDbc(CmdData); } catch (Exception ex) { //LogService.Info($"时间:{DateTime.Now.ToString()}-【Meter】-{ex.Message}"); } } }); } /// /// 循环获取CAN消息(使用预分配缓冲区,避免每次循环内存分配/释放) /// public void StartCycleReviceCanMsg1() { if (CycleReviceTask != null && !CycleReviceTask.IsCompleted) { return; } // 预分配接收缓冲区(仅在首次或已释放后分配) EnsureRecvBufferAllocated(); CycleReviceTask = Task.Run(async () => { var msgSize = Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)); double[] valueDouble = new double[1]; while (IsCycleRevice) { await Task.Delay(ReviceCycle); try { int CanNum; lock (_recvBufferLock) { if (RecvMsgBufferPtr == IntPtr.Zero) break; CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, ReadCANIndex, RecvMsgBufferPtr, RecvBufferSize); if (CanNum > 0) { for (int i = 0; i < CanNum; i++) { RecvMsgBuffer[i] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure( (IntPtr)(RecvMsgBufferPtr + i * msgSize), typeof(USB2CAN.CAN_MSG)); HighSpeedDataService.AppendOrUpdateMsg(new Models.HighSpeed.CommMsg() { Category = "CAN", MsgInfo = "0x" + RecvMsgBuffer[i].ID.ToString("X8"), MsgData = BitConverter.ToString(RecvMsgBuffer[i].Data), Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") }); } } } if (CanNum > 0) { lock (_dbcParserLock) { lock (_recvBufferLock) { if (RecvMsgBufferPtr == IntPtr.Zero) break; CAN_DBCParser.DBC_SyncCANMsgToValue(DBCHandle, RecvMsgBufferPtr, CanNum); } foreach (var item in ListCanDbcModel) { valueDouble[0] = 0; CAN_DBCParser.DBC_GetSignalValue(DBCHandle, new StringBuilder(item.MsgName), new StringBuilder(item.SignalName), valueDouble); item.SignalRtValue = valueDouble[0].ToString(); } } } } catch (Exception ex) { Console.WriteLine($"CAN循环接收异常: {ex.Message}"); } } }); } // 保护接收缓冲的并发锁(接收读与关闭释放之间的互斥) private readonly object RecvBufferSync = new object(); //StringBuilder ValueSb = new StringBuilder(16); double[] ValueDouble = new double[5]; // 接收缓冲池(重用,避免每轮分配) //private IntPtr RecvMsgBufferPtr = IntPtr.Zero; private int RecvMsgBufferCapacity = 1024; private readonly int CanMsgSize = Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)); // 名称 StringBuilder 缓存(DBC 调用复用,避免频繁分配) private readonly Dictionary MsgNameSBCache = new Dictionary(StringComparer.Ordinal); private readonly Dictionary SigNameSBCache = new Dictionary(StringComparer.Ordinal); // 控制台调试输出开关(默认关闭,防止日志风暴) public bool EnableConsoleDebugLog { get; set; } = true; // 保护接收缓冲的并发锁(接收读与关闭释放之间的互斥) //private readonly object RecvBufferSync = new object(); private StringBuilder GetMsgSB(string key) => GetCachedSB(MsgNameSBCache, key); private StringBuilder GetSigSB(string key) => GetCachedSB(SigNameSBCache, key); private StringBuilder GetCachedSB(Dictionary cache, string key) { key ??= string.Empty; if (cache.TryGetValue(key, out var sb)) return sb; var nsb = new StringBuilder(key); cache[key] = nsb; return nsb; } /// /// 启动后台循环接收 CAN 报文,并同步到 DBC 信号实时值。 /// /// /// 关键点: /// - 使用 CAN_GetMsgWithSize 从设备内部 FIFO 拉取报文; /// - 使用 作为重用缓冲,避免每轮申请/释放非托管内存导致碎片与性能问题; /// - 使用 互斥,避免指针并发释放。 /// public void StartCycleReviceCanMsg() { // 防止重复启动,若已有任务在运行则直接返回 if (CycleReviceTask != null && !CycleReviceTask.IsCompleted) { return; } //monitorValueLog CycleReviceTask = Task.Run(async () => { try { while (IsCycleRevice) { await Task.Delay(ReviceCycle); try { // 另一个CAN通道读取数据(与 CloseDevice 释放互斥,保护指针安全) IntPtr msgPtRead; int CanNum; lock (RecvBufferSync) { if (RecvMsgBufferPtr == IntPtr.Zero) { RecvMsgBufferPtr = Marshal.AllocHGlobal(CanMsgSize * RecvMsgBufferCapacity); //LoggerService.Info("申请 RecvMsgBufferPtr"); } msgPtRead = RecvMsgBufferPtr; CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, ReadCANIndex, msgPtRead, RecvMsgBufferCapacity); //int CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, 1, msgPtRead, RecvMsgBufferCapacity);//测试用,CAN卡 CAN1和CAN2 短接时测试用 //monitorValueLog.UpdateValue4(CanNum); int SyncCANMsgToValue = CAN_DBCParser.DBC_PARSER_OK; if (CanNum > 0) { //仅当确实收到了报文,才把缓冲区交给 DBC 解析器,避免负数 / 0 传给原生函数造成越界读后堆腐败 SyncCANMsgToValue = CAN_DBCParser.DBC_SyncCANMsgToValue(DBCHandle, msgPtRead, CanNum); IsReviceOk = true; if (EnableConsoleDebugLog) Console.WriteLine("Read CanMsgNum = {0}", CanNum); for (int i = 0; i < CanNum; i++) { var msgPtr = (IntPtr)(msgPtRead + i * CanMsgSize); var msg = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(msgPtr, typeof(USB2CAN.CAN_MSG)); if (EnableConsoleDebugLog) { Console.WriteLine("CanMsg[{0}].ID = 0x{1}", i, msg.ID.ToString("X8")); Console.WriteLine("CanMsg[{0}].TimeStamp = {1}", i, msg.TimeStamp); Console.Write("CanMsg[{0}].Data = ", i); for (int j = 0; j < msg.DataLen; j++) { Console.Write("{0} ", msg.Data[j].ToString("X2")); } Console.WriteLine(""); } //// 报文给高速记录的服务 //HighSpeedDataService.AppendOrUpdateMsg(new Models.HighSpeed.CommMsg() //{ // Category = "CAN", // MsgInfo = "0x" + msg.ID.ToString("X8"), // MsgData = BitConverter.ToString(msg.Data), // Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") //}); } } else if (CanNum == 0) { IsReviceOk = false; if (EnableConsoleDebugLog) Console.WriteLine("No CAN data!"); } else { IsReviceOk = false; if (EnableConsoleDebugLog) Console.WriteLine("Get CAN data error!"); } //【已修复】原来此处无条件调用 DBC_SyncCANMsgToValue(DBCHandle, msgPtRead, CanNum) //当 CanNum == 0(无报文)或 CanNum < 0(读失败)时把无效 MsgLen 传给原生 DBC 解析器, //会越界读 msgPtRead 缓冲后的内存导致 GC 堆腐败,运行一段时间后随机位置 AV(0xC0000005)。 //现已挪到 CanNum > 0 分支内(参考 HASCO_Simple25001 的 StartCycleReviceCanMsg1 写法)。 } //循环获取消息的数据 foreach (var item in ListCanDbcModel) { // 复用 StringBuilder 缓存,避免频繁分配 var msgNameSB = GetMsgSB(item.MsgName); var sigNameSB = GetSigSB(item.SignalName); CAN_DBCParser.DBC_GetSignalValue(DBCHandle, msgNameSB, sigNameSB, ValueDouble); item.SignalRtValue = ValueDouble[0].ToString(); //Console.Write(ValueSb.ToString()); } // 缓冲区在 CloseDevice 或任务退出的 finally 中统一释放,避免频繁申请/释放 } catch (Exception ex) { IsReviceOk = false; //LoggerService.Info($"CAN指令接收出现异常:{ex.Message}"); } //finally //{ // IsReviceOk = false; //} } } finally { IsReviceOk = false; //LoggerService.Info("CAN指令接收 finally结束"); // 接收任务退出时释放接收缓冲,避免仅停止接收时的缓冲常驻 lock (RecvBufferSync) { if (RecvMsgBufferPtr != IntPtr.Zero) { try { Marshal.FreeHGlobal(RecvMsgBufferPtr); } catch { } finally { RecvMsgBufferPtr = IntPtr.Zero; } } } } }); } private bool _IsReviceOk; /// /// 最近一次接收是否成功。 /// /// /// 该状态反映最近一次轮询读取 CAN_GetMsgWithSize 的结果: /// - CanNum > 0:成功收到数据; /// - CanNum == 0:本轮无数据; /// - CanNum < 0:调用失败。 /// public bool IsReviceOk { get { return _IsReviceOk; } set { if (_IsReviceOk != value) { RaisePropertyChanged(); _IsReviceOk = value; } } } /// /// 确保接收缓冲区已分配 /// private void EnsureRecvBufferAllocated() { lock (_recvBufferLock) { if (RecvMsgBufferPtr == IntPtr.Zero) { RecvMsgBufferPtr = Marshal.AllocHGlobal( Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)) * RecvBufferSize); } } } /// /// 释放接收缓冲区 /// private void FreeRecvBuffer() { lock (_recvBufferLock) { if (RecvMsgBufferPtr != IntPtr.Zero) { Marshal.FreeHGlobal(RecvMsgBufferPtr); RecvMsgBufferPtr = IntPtr.Zero; } } } #region 精确发送报文数据 // 添加取消标记源字段用于停止任务 private CancellationTokenSource? CycleSendCts; /// /// 计算每毫秒对应的ticks数(只需计算一次) /// private double TicksPerMs; // 类成员变量定义 精确记时用 private readonly Stopwatch Stopwatcher = new Stopwatch(); private long NextExecutionTime; // 计算需要等待的时间 private long CurrentTime; private long DelayTicks; private int DelayMs; /// /// 精确周期发送CAN数据 /// public void StartPrecisionCycleSendMsg() { if (CycleSendTask != null && !CycleSendTask.IsCompleted) { return; } // 创建取消标记源 用于控制任务的取消 允许在需要时通过取消令牌来优雅停止任务 var cancellationTokenSource = new CancellationTokenSource(); var token = cancellationTokenSource.Token; // 保存取消标记,以便在停止时使用 CycleSendCts = cancellationTokenSource;//将取消标记源保存到类的成员变量CycleSendCts,这样在外部调用停止方法时可以访问它 NextExecutionTime = 0;//初始化NextExecutionTime为0,这个变量用于记录下一次执行的目标时间点 CycleSendTask = Task.Run(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; 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逻辑 //{ // SendCanMessagesByDbc(CmdData); //} // 执行发送CAN逻辑 { var GroupMsg = CmdData.GroupBy(x => x.MsgName); USB2CAN.CAN_MSG[] CanMsg = new USB2CAN.CAN_MSG[GroupMsg.Count()]; for (int i = 0; i < GroupMsg.Count(); i++) { CanMsg[i] = new USB2CAN.CAN_MSG(); CanMsg[i].Data = new Byte[64]; } // 发送构帧临时缓冲:每轮申请/释放,避免与其他线程共享同一指针导致并发问题 IntPtr msgPtSend = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG))); int Index = 0; //循环给MSG赋值数据 foreach (var itemMsg in GroupMsg) { foreach (var itemSignal in itemMsg) { CAN_DBCParser.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue); } CAN_DBCParser.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), msgPtSend); CanMsg[Index] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(msgPtSend, typeof(USB2CAN.CAN_MSG)); Index++; } //通过DBC写入数据后生成CanMsg //将信号值填入CAN消息里面 // 释放非托管缓冲区(务必释放,否则长时间运行会造成内存泄漏) Marshal.FreeHGlobal(msgPtSend); //发送CAN数据 int SendedNum = USB2CAN.CAN_SendMsg(DevHandle, WriteCANIndex, CanMsg, (uint)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) { // 任务被取消,正常退出 break; } catch (Exception ex) { Console.WriteLine($"CAN周期发送异常: {ex.Message}"); // 短暂暂停避免异常情况下CPU占用过高 await Task.Delay(10, token); } } } catch (Exception ex) { Console.WriteLine($"CAN精确周期发送异常: {ex.Message}"); } finally { // 确保在任何情况下(正常退出、异常、取消)都会停止计时器 Stopwatcher.Stop(); Console.WriteLine("CAN周期发送任务已结束,资源已清理"); } }, token); } private bool _IsSendOk; /// /// 最近一次发送是否成功。 /// /// /// - 软件循环发送路径:由 CAN_SendMsg 返回值决定; /// - 调度表更新路径:由 CAN_UpdateSchedule 返回值决定。 /// public bool IsSendOk { get { return _IsSendOk; } set { if (_IsSendOk != value) { RaisePropertyChanged(); _IsSendOk = value; } //RaisePropertyChanged(); } } /// /// 修改停止发送的方法 /// public void StopCycleSendMsg() { IsCycleSend = false; CycleSendCts?.Cancel(); } #endregion /// /// 关闭设备 /// public void CloseDevice() { IsCycleRevice = false; IsCycleSend = false; // 停止精确发送并释放CTS try { StopCycleSendMsg(); } catch (Exception ex) { Console.WriteLine($"停止CAN发送异常: {ex.Message}"); } // 等待发送任务结束 try { var sendTask = CycleSendTask; if (sendTask != null && !sendTask.IsCompleted) { sendTask.Wait(TimeSpan.FromMilliseconds(SendCycle * 3 + 500)); } } catch (Exception ex) { Console.WriteLine($"等待CAN发送任务结束异常: {ex.Message}"); } // 等待接收任务结束 try { var recvTask = CycleReviceTask; if (recvTask != null && !recvTask.IsCompleted) { recvTask.Wait(TimeSpan.FromMilliseconds(ReviceCycle + 500)); } } catch (Exception ex) { Console.WriteLine($"等待CAN接收任务结束异常: {ex.Message}"); } // 释放接收缓冲区 FreeRecvBuffer(); // 释放CTS资源 try { CycleSendCts?.Dispose(); CycleSendCts = null; } catch { } //关闭设备 USB_DEVICE.USB_CloseDevice(DevHandle); OpenState = false; DbcParserState = false; } } }