diff --git a/CapMachine.Wpf/CanDrive/ToomossCan.cs b/CapMachine.Wpf/CanDrive/ToomossCan.cs
index 6ea2ef9..8325922 100644
--- a/CapMachine.Wpf/CanDrive/ToomossCan.cs
+++ b/CapMachine.Wpf/CanDrive/ToomossCan.cs
@@ -633,6 +633,195 @@ namespace CapMachine.Wpf.CanDrive
});
}
+
+ #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;
+
+ private static readonly Random _random = new Random();
+
+ ///
+ /// 精确周期发送CAN数据
+ ///
+ 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;
+ 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);
+ 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)
+ {
+ // 确保在任何情况下(正常退出、异常、取消)都会停止计时器
+ Stopwatcher.Stop();
+
+ // 清理其他可能的资源
+ Console.WriteLine("CAN周期发送任务已结束,资源已清理");
+ }
+ finally
+ {
+ // 确保在任何情况下(正常退出、异常、取消)都会停止计时器
+ Stopwatcher.Stop();
+ }
+
+ }, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
+ }
+
+
+ ///
+ /// 修改停止发送的方法
+ ///
+ public void StopCycleSendMsg()
+ {
+ IsCycleSend = false;
+ CycleSendCts?.Cancel();
+ }
+
+ #endregion
+
+
+
///
/// 接受CAN消息
///
diff --git a/CapMachine.Wpf/Services/CanDriveService.cs b/CapMachine.Wpf/Services/CanDriveService.cs
index 42fd28c..a2c6820 100644
--- a/CapMachine.Wpf/Services/CanDriveService.cs
+++ b/CapMachine.Wpf/Services/CanDriveService.cs
@@ -230,7 +230,7 @@ namespace CapMachine.Wpf.Services
if (ListCanDbcModel.Count > 0)
{
ToomossCanDrive.IsCycleRevice = true;
- ToomossCanDrive.StartCycleReviceCanMsg();
+ ToomossCanDrive.StartPrecisionCycleSendMsg();
}
else
{