Files
CapMachine/CapMachine.Wpf/CanDrive/ToomossCan.cs
2026-05-14 22:04:31 +08:00

1146 lines
46 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.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
{
/// <summary>
/// Toomoss CAN
/// </summary>
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;
/// <summary>
/// 预分配的接收缓冲区指针,避免每次循环分配/释放
/// </summary>
private IntPtr RecvMsgBufferPtr = IntPtr.Zero;
/// <summary>
/// 接收缓冲区对应的托管数组
/// </summary>
private readonly USB2CAN.CAN_MSG[] RecvMsgBuffer = new USB2CAN.CAN_MSG[RecvBufferSize];
/// <summary>
/// 实例化函数
/// </summary>
public ToomossCan(IContainerProvider containerProvider)
{
ContainerProvider = containerProvider;
HighSpeedDataService = ContainerProvider.Resolve<HighSpeedDataService>();
//Stopwatch.Frequency表示高精度计时器每秒的计数次数ticks/秒每毫秒的ticks数 = 每秒的ticks数 ÷ 1000
TicksPerMs = Stopwatch.Frequency / 1000.0;
}
/// <summary>
/// 开始CAN的驱动
/// </summary>
public void StartCanDrive()
{
IsExistsDllFile();
ScanDevice();
OpenDevice();
GetDeviceInfo();
GetCANConfig();
InitCAN();
}
/// <summary>
/// HighSpeedDataService 实例
/// </summary>
public HighSpeedDataService HighSpeedDataService { get; set; }
/// <summary>
/// 开始Dbc文件写入
/// </summary>
public ObservableCollection<CanDbcModel> StartDbc(string DbcPath)
{
DBC_Parser(DbcPath);
return ListCanDbcModel;
}
///// <summary>
///// 获取Can DBC数据集合
///// </summary>
//public void LoadCanDbcData(ObservableCollection<CanDbcModel> canDbcModels)
//{
// ListCanDbcModel = canDbcModels;
//}
/// <summary>
/// Dbc消息集合
/// 包括读取的实时值和数据
/// </summary>
public ObservableCollection<CanDbcModel> ListCanDbcModel { get; set; } = new ObservableCollection<CanDbcModel>();
#region
/// <summary>
/// 设备固件信息
/// </summary>
public USB_DEVICE.DEVICE_INFO DevInfo = new USB_DEVICE.DEVICE_INFO();
/// <summary>
/// CAN Config
/// </summary>
USB2CAN.CAN_INIT_CONFIG CANConfig = new USB2CAN.CAN_INIT_CONFIG();
/// <summary>
/// DBC加载后获取的Handle
/// DBC Handle
/// </summary>
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>
/// 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则说明调用该函数失败。
/// </summary>
public Byte WriteCANIndex { get; set; } = 0;
/// <summary>
/// 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则说明调用该函数失败。
/// </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_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;
}
}
/// <summary>
/// ******************【3】*********************
/// 打开设备
/// </summary>
/// <returns></returns>
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;
}
}
/// <summary>
/// ******************【4】*********************
/// 获取设备的固件信息
/// </summary>
/// <returns></returns>
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;
}
}
/// <summary>
/// ******************【5】*********************
/// 获取设备Config配置
/// </summary>
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!");
}
}
/// <summary>
/// ******************【6】*********************
/// 初始化CAN
/// </summary>
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("");
}
/// <summary>
/// ******************【7】*********************
/// DBC解析
/// </summary>
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;
}
}
/// <summary>
/// 发送CAN数据
/// 发送一次
/// </summary>
public void SendCanMsg(List<CanCmdData> CmdData)
{
SendCanMessagesByDbc(CmdData);
}
private void SendCanMessagesByDbc(IEnumerable<CanCmdData> 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;
/// <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; } = 100;
/// <summary>
/// 循环接受数据
/// </summary>
public ushort ReviceCycle { get; set; } = 500;
/// <summary>
/// CycleRevice 扫描Task
/// </summary>
private Task? CycleReviceTask { get; set; }
/// <summary>
/// CycleSend 扫描Task
/// </summary>
private Task? CycleSendTask { get; set; }
StringBuilder ValueSb = new StringBuilder(16);
/// <summary>
/// 要发送的数据
/// </summary>
public List<CanCmdData> CmdData { get; set; } = new List<CanCmdData>();
/// <summary>
/// 循环发送数据
/// </summary>
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}");
}
}
});
}
/// <summary>
/// 循环获取CAN消息使用预分配缓冲区避免每次循环内存分配/释放)
/// </summary>
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<string, StringBuilder> MsgNameSBCache = new Dictionary<string, StringBuilder>(StringComparer.Ordinal);
private readonly Dictionary<string, StringBuilder> SigNameSBCache = new Dictionary<string, StringBuilder>(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<string, StringBuilder> 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;
}
/// <summary>
/// 启动后台循环接收 CAN 报文,并同步到 DBC 信号实时值。
/// </summary>
/// <remarks>
/// 关键点:
/// - 使用 CAN_GetMsgWithSize 从设备内部 FIFO 拉取报文;
/// - 使用 <see cref="RecvMsgBufferPtr"/> 作为重用缓冲,避免每轮申请/释放非托管内存导致碎片与性能问题;
/// - 使用 <see cref="RecvBufferSync"/> 与 <see cref="CloseDevice"/> 互斥,避免指针并发释放。
/// </remarks>
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;
/// <summary>
/// 最近一次接收是否成功。
/// </summary>
/// <remarks>
/// 该状态反映最近一次轮询读取 CAN_GetMsgWithSize 的结果:
/// - CanNum &gt; 0成功收到数据
/// - CanNum == 0本轮无数据
/// - CanNum &lt; 0调用失败。
/// </remarks>
public bool IsReviceOk
{
get { return _IsReviceOk; }
set
{
if (_IsReviceOk != value)
{
RaisePropertyChanged();
_IsReviceOk = value;
}
}
}
/// <summary>
/// 确保接收缓冲区已分配
/// </summary>
private void EnsureRecvBufferAllocated()
{
lock (_recvBufferLock)
{
if (RecvMsgBufferPtr == IntPtr.Zero)
{
RecvMsgBufferPtr = Marshal.AllocHGlobal(
Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)) * RecvBufferSize);
}
}
}
/// <summary>
/// 释放接收缓冲区
/// </summary>
private void FreeRecvBuffer()
{
lock (_recvBufferLock)
{
if (RecvMsgBufferPtr != IntPtr.Zero)
{
Marshal.FreeHGlobal(RecvMsgBufferPtr);
RecvMsgBufferPtr = IntPtr.Zero;
}
}
}
#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;
/// <summary>
/// 精确周期发送CAN数据
/// </summary>
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;
/// <summary>
/// 最近一次发送是否成功。
/// </summary>
/// <remarks>
/// - 软件循环发送路径:由 CAN_SendMsg 返回值决定;
/// - 调度表更新路径:由 CAN_UpdateSchedule 返回值决定。
/// </remarks>
public bool IsSendOk
{
get { return _IsSendOk; }
set
{
if (_IsSendOk != value)
{
RaisePropertyChanged();
_IsSendOk = value;
}
//RaisePropertyChanged();
}
}
/// <summary>
/// 修改停止发送的方法
/// </summary>
public void StopCycleSendMsg()
{
IsCycleSend = false;
CycleSendCts?.Cancel();
}
#endregion
/// <summary>
/// 关闭设备
/// </summary>
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;
}
}
}