From b52e9f7857a32e49cdc72f4e1335e37337662329 Mon Sep 17 00:00:00 2001 From: Tyrone CT Date: Wed, 22 Oct 2025 15:04:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9A=E6=97=B6=E5=99=A8=E5=8F=91=E9=80=81?= =?UTF-8?q?=20=E5=88=9D=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CapMachine.Wpf/CanDrive/ToomossCan.cs | 233 +++++++++++++++++++++++++- 1 file changed, 232 insertions(+), 1 deletion(-) diff --git a/CapMachine.Wpf/CanDrive/ToomossCan.cs b/CapMachine.Wpf/CanDrive/ToomossCan.cs index 3df7b1a..113f969 100644 --- a/CapMachine.Wpf/CanDrive/ToomossCan.cs +++ b/CapMachine.Wpf/CanDrive/ToomossCan.cs @@ -615,6 +615,234 @@ namespace CapMachine.Wpf.CanDrive }); } + #region 定时器发送报文数据 + + // 定时器发送:采用单次触发+校准的方式避免周期漂移,减少长时间运行误差 + // 说明:不修改 StartPrecisionCycleSendMsg 的任何内容;此处仅实现定时器版本 + + // 定时器与资源 + private System.Threading.Timer _sendTimer; + private readonly object _sendTimerSync = new object(); + private volatile int _sendTimerRunningFlag = 0; // 防止回调重入 + private int _sendTimerPeriodMs = 100; // 默认100ms + private readonly Stopwatch _sendTimerWatch = new Stopwatch(); + private long _sendTimerNextTicks; + + // 发送缓冲重用,减少分配 + private USB2CAN.CAN_MSG[] _timerCanMsgBuffer; + private IntPtr _timerMsgPtr = IntPtr.Zero; + + /// + /// 启动基于定时器的周期发送(毫秒)。支持典型的50/100/200ms等周期。 + /// 使用单次触发+绝对时间校准的算法,尽量减少漂移;并使用重用缓冲降低内存分配。 + /// + /// 发送周期(毫秒) + public void StartTimerCycleSendMsg(int periodMs) + { + if (periodMs <= 0) periodMs = 1; + + lock (_sendTimerSync) + { + // 先停止旧的 + StopTimerCycleSendMsg_NoLock(); + + _sendTimerPeriodMs = periodMs; + IsCycleSend = true; + + if (_timerMsgPtr == IntPtr.Zero) + { + _timerMsgPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG))); + } + + _sendTimerWatch.Restart(); + _sendTimerNextTicks = _sendTimerWatch.ElapsedTicks + (long)(_sendTimerPeriodMs * TicksPerMs); + + // 使用单次触发模式,每次回调后手动调度下一次,便于按绝对时间校准 + _sendTimer = new System.Threading.Timer(SendTimerCallback, null, 0, System.Threading.Timeout.Infinite); + } + } + + /// + /// 启动基于定时器的周期发送(使用 SendCycle 属性作为周期参数) + /// + public void StartTimerCycleSendMsg() + { + StartTimerCycleSendMsg((int)SendCycle); + } + + /// + /// 便捷方法:启动50ms周期发送 + /// + public void StartTimerCycleSendMsg50ms() => StartTimerCycleSendMsg(50); + + /// + /// 便捷方法:启动100ms周期发送 + /// + public void StartTimerCycleSendMsg100ms() => StartTimerCycleSendMsg(100); + + /// + /// 便捷方法:启动200ms周期发送 + /// + public void StartTimerCycleSendMsg200ms() => StartTimerCycleSendMsg(200); + + /// + /// 停止定时器周期发送并释放资源 + /// + public void StopTimerCycleSendMsg() + { + lock (_sendTimerSync) + { + IsCycleSend = false; + StopTimerCycleSendMsg_NoLock(); + } + } + + private void StopTimerCycleSendMsg_NoLock() + { + var t = _sendTimer; + _sendTimer = null; + if (t != null) + { + try { t.Dispose(); } catch { } + } + + // 等待在途回调结束,避免释放资源时发生竞争 + System.Threading.SpinWait.SpinUntil(() => System.Threading.Interlocked.CompareExchange(ref _sendTimerRunningFlag, 0, 0) == 0, 200); + + _sendTimerWatch.Stop(); + + if (_timerMsgPtr != IntPtr.Zero) + { + try { Marshal.FreeHGlobal(_timerMsgPtr); } catch { } finally { _timerMsgPtr = IntPtr.Zero; } + } + _timerCanMsgBuffer = null; // 让GC回收 + IsSendOk = false; + } + + // 定时器回调(单次触发);回调结束时会按绝对时间校准下一次触发 + private void SendTimerCallback(object state) + { + try + { + if (!IsCycleSend) return; + + // 防止回调重入(当某次发送耗时超过周期时避免并发) + if (System.Threading.Interlocked.Exchange(ref _sendTimerRunningFlag, 1) == 1) + { + ScheduleNextTick(); + return; + } + + var localCmd = CmdData; // 快照引用,避免枚举时被替换 + if (localCmd == null || localCmd.Count == 0) + { + IsSendOk = false; + return; + } + + // 依据消息名进行分组(与 StartPrecisionCycleSendMsg 的发送逻辑一致) + IEnumerable> groupMsg; + try + { + groupMsg = localCmd.GroupBy(x => x.MsgName).ToList(); // ToList 避免重复枚举 + } + catch + { + // 罕见并发异常,跳过本次 + IsSendOk = false; + return; + } + + int msgCount = groupMsg.Count(); + if (msgCount <= 0) + { + IsSendOk = false; + return; + } + + // 确保发送缓冲容量,尽量复用,减少频繁分配 + if (_timerCanMsgBuffer == null || _timerCanMsgBuffer.Length != msgCount) + { + _timerCanMsgBuffer = new USB2CAN.CAN_MSG[msgCount]; + for (int i = 0; i < msgCount; i++) + { + _timerCanMsgBuffer[i] = new USB2CAN.CAN_MSG(); + _timerCanMsgBuffer[i].Data = new byte[64]; + } + } + + int index = 0; + foreach (var itemMsg in groupMsg) + { + foreach (var itemSignal in itemMsg) + { + // 与 StartPrecisionCycleSendMsg 保持一致的调用形态,避免与其他线程共享缓存的并发问题 + CAN_DBCParser.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue); + } + + // 将信号同步到 CAN 帧 + CAN_DBCParser.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), _timerMsgPtr); + _timerCanMsgBuffer[index] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(_timerMsgPtr, typeof(USB2CAN.CAN_MSG)); + index++; + } + + // 发送 CAN 数据 + int sendedNum = USB2CAN.CAN_SendMsg(DevHandle, WriteCANIndex, _timerCanMsgBuffer, (uint)_timerCanMsgBuffer.Length); + IsSendOk = sendedNum >= 0; + } + catch (Exception ex) + { + IsSendOk = false; + LoggerService?.Info($"定时器周期发送CAN数据异常: {ex.Message}"); + } + finally + { + // 标记回调完成并调度下一次 + System.Threading.Interlocked.Exchange(ref _sendTimerRunningFlag, 0); + ScheduleNextTick(); + } + } + + // 计算并调度下一次触发时间(基于绝对时间进行校准,减少漂移) + private void ScheduleNextTick() + { + try + { + if (!IsCycleSend) return; + var timer = _sendTimer; + if (timer == null) return; + + long now = _sendTimerWatch.ElapsedTicks; + long periodTicks = (long)(_sendTimerPeriodMs * TicksPerMs); + + if (_sendTimerNextTicks == 0) + { + _sendTimerNextTicks = now + periodTicks; + } + else if (now >= _sendTimerNextTicks) + { + // 若已错过计划时间,跳至最近的未来时刻,避免累计漂移 + long missed = (now - _sendTimerNextTicks) / periodTicks + 1; + _sendTimerNextTicks += missed * periodTicks; + } + + long dueTicks = _sendTimerNextTicks - now; + int dueMs = dueTicks <= 0 ? 0 : (int)(dueTicks / TicksPerMs); + + timer.Change(dueMs, System.Threading.Timeout.Infinite); + } + catch (ObjectDisposedException) + { + // 计时器已被释放,忽略 + } + catch (Exception ex) + { + LoggerService?.Info($"定时器调度异常: {ex.Message}"); + } + } + + #endregion + #region 精确发送报文数据 @@ -1256,7 +1484,7 @@ namespace CapMachine.Wpf.CanDrive { IsSendOk = true; //Console.WriteLine($"Update CAN Schedule Success -- SchTabIndex:{(byte)0} -- MsgIndex:{(byte)(0)} "); - + monitorValueLog.UpdateValue3(ret); } else { @@ -1480,6 +1708,9 @@ namespace CapMachine.Wpf.CanDrive StopSchedule(); } + // 确保定时器发送被停止并释放资源 + try { StopTimerCycleSendMsg(); } catch { } + // 等待接收任务结束后释放非托管缓冲区,避免并发释放 try {