feat: add CAN board test tool and timing safeguards
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SDCAcqDacTester.Can;
|
||||
|
||||
public sealed record CanFrame(uint Id, bool IsExtended, byte[] Data, DateTime Timestamp);
|
||||
|
||||
public sealed class ControlCanTransport : IDisposable
|
||||
{
|
||||
private const uint DeviceTypeUsbCan2 = 4;
|
||||
private const uint StatusOk = 1;
|
||||
private CancellationTokenSource? _receiveCancel;
|
||||
private Task? _receiveTask;
|
||||
private uint _deviceIndex;
|
||||
private uint _channelIndex;
|
||||
|
||||
public bool IsOpen { get; private set; }
|
||||
public event EventHandler<CanFrame>? FrameReceived;
|
||||
public event EventHandler<string>? ReceiveError;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct VciInitConfig
|
||||
{
|
||||
public uint AccCode;
|
||||
public uint AccMask;
|
||||
public uint Reserved;
|
||||
public byte Filter;
|
||||
public byte Timing0;
|
||||
public byte Timing1;
|
||||
public byte Mode;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
private struct VciCanObj
|
||||
{
|
||||
public uint Id;
|
||||
public uint TimeStamp;
|
||||
public byte TimeFlag;
|
||||
public byte SendType;
|
||||
public byte RemoteFlag;
|
||||
public byte ExternFlag;
|
||||
public byte DataLen;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public byte[] Data;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public byte[] Reserved;
|
||||
}
|
||||
|
||||
[DllImport("ControlCAN.dll", CallingConvention = CallingConvention.StdCall)]
|
||||
private static extern uint VCI_OpenDevice(uint deviceType, uint deviceIndex, uint reserved);
|
||||
[DllImport("ControlCAN.dll", CallingConvention = CallingConvention.StdCall)]
|
||||
private static extern uint VCI_CloseDevice(uint deviceType, uint deviceIndex);
|
||||
[DllImport("ControlCAN.dll", CallingConvention = CallingConvention.StdCall)]
|
||||
private static extern uint VCI_InitCAN(uint deviceType, uint deviceIndex, uint canIndex, ref VciInitConfig config);
|
||||
[DllImport("ControlCAN.dll", CallingConvention = CallingConvention.StdCall)]
|
||||
private static extern uint VCI_StartCAN(uint deviceType, uint deviceIndex, uint canIndex);
|
||||
[DllImport("ControlCAN.dll", CallingConvention = CallingConvention.StdCall)]
|
||||
private static extern uint VCI_ResetCAN(uint deviceType, uint deviceIndex, uint canIndex);
|
||||
[DllImport("ControlCAN.dll", CallingConvention = CallingConvention.StdCall)]
|
||||
private static extern uint VCI_Transmit(uint deviceType, uint deviceIndex, uint canIndex, [In] VciCanObj[] send, uint length);
|
||||
[DllImport("ControlCAN.dll", CallingConvention = CallingConvention.StdCall)]
|
||||
private static extern uint VCI_Receive(uint deviceType, uint deviceIndex, uint canIndex, [Out] VciCanObj[] receive, uint length, int waitTime);
|
||||
|
||||
public void Open(uint deviceIndex, uint channelIndex)
|
||||
{
|
||||
if (IsOpen) return;
|
||||
_deviceIndex = deviceIndex;
|
||||
_channelIndex = channelIndex;
|
||||
if (VCI_OpenDevice(DeviceTypeUsbCan2, deviceIndex, 0) != StatusOk)
|
||||
throw new InvalidOperationException("打开 USBCAN-II 失败。请确认设备未被其他程序占用,并检查 ControlCAN.dll。 ");
|
||||
|
||||
// SJA1000 timing for 500 kbit/s, normal mode so the adapter ACKs board frames.
|
||||
var config = new VciInitConfig { AccCode = 0, AccMask = 0xFFFFFFFF, Filter = 1, Timing0 = 0x00, Timing1 = 0x1C, Mode = 0 };
|
||||
if (VCI_InitCAN(DeviceTypeUsbCan2, deviceIndex, channelIndex, ref config) != StatusOk)
|
||||
{
|
||||
VCI_CloseDevice(DeviceTypeUsbCan2, deviceIndex);
|
||||
throw new InvalidOperationException("初始化 CAN 失败(500 kbit/s)。");
|
||||
}
|
||||
if (VCI_StartCAN(DeviceTypeUsbCan2, deviceIndex, channelIndex) != StatusOk)
|
||||
{
|
||||
VCI_ResetCAN(DeviceTypeUsbCan2, deviceIndex, channelIndex);
|
||||
VCI_CloseDevice(DeviceTypeUsbCan2, deviceIndex);
|
||||
throw new InvalidOperationException("启动 CAN 通道失败。");
|
||||
}
|
||||
IsOpen = true;
|
||||
_receiveCancel = new CancellationTokenSource();
|
||||
_receiveTask = Task.Run(() => ReceiveLoop(_receiveCancel.Token));
|
||||
}
|
||||
|
||||
public void SendStandard(uint id, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (!IsOpen) throw new InvalidOperationException("CAN 未连接。");
|
||||
if (id > 0x7FF || data.Length > 8) throw new ArgumentOutOfRangeException(nameof(id));
|
||||
var payload = new byte[8];
|
||||
data.CopyTo(payload);
|
||||
var frame = new VciCanObj { Id = id, SendType = 0, RemoteFlag = 0, ExternFlag = 0, DataLen = (byte)data.Length, Data = payload, Reserved = new byte[3] };
|
||||
if (VCI_Transmit(DeviceTypeUsbCan2, _deviceIndex, _channelIndex, [frame], 1) != StatusOk)
|
||||
throw new InvalidOperationException("CAN 发送失败。");
|
||||
}
|
||||
|
||||
private void ReceiveLoop(CancellationToken token)
|
||||
{
|
||||
var buffer = Enumerable.Range(0, 100).Select(_ => new VciCanObj { Data = new byte[8], Reserved = new byte[3] }).ToArray();
|
||||
try
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
uint count = VCI_Receive(DeviceTypeUsbCan2, _deviceIndex, _channelIndex, buffer, (uint)buffer.Length, 50);
|
||||
if (count == 0 || count == uint.MaxValue) continue;
|
||||
for (var i = 0; i < Math.Min(count, (uint)buffer.Length); i++)
|
||||
{
|
||||
var item = buffer[i];
|
||||
if (item.RemoteFlag != 0) continue;
|
||||
FrameReceived?.Invoke(this, new CanFrame(item.Id, item.ExternFlag != 0, item.Data.Take(Math.Min(item.DataLen, (byte)8)).ToArray(), DateTime.Now));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!token.IsCancellationRequested) { ReceiveError?.Invoke(this, ex.Message); }
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (!IsOpen) return;
|
||||
_receiveCancel?.Cancel();
|
||||
_receiveTask?.Wait(300);
|
||||
VCI_ResetCAN(DeviceTypeUsbCan2, _deviceIndex, _channelIndex);
|
||||
VCI_CloseDevice(DeviceTypeUsbCan2, _deviceIndex);
|
||||
_receiveCancel?.Dispose();
|
||||
_receiveCancel = null;
|
||||
IsOpen = false;
|
||||
}
|
||||
public void Dispose() => Close();
|
||||
}
|
||||
Reference in New Issue
Block a user