添加xcpcore,添加加载dbc至单一通道

This commit is contained in:
“hsc”
2026-10-15 14:05:18 +08:00
parent a2a62a0327
commit 981bc1e558
12 changed files with 983 additions and 225 deletions
+5 -1
View File
@@ -10,9 +10,13 @@
<ItemGroup>
<ProjectReference Include="..\..\TSMasterCAN\TSMasterCAN.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Interop.TSMasterAPI">
<HintPath>..\TOSUNCAN\Interop.TSMasterAPI.dll</HintPath>
<HintPath>..\..\TSMasterCAN\Interop.TSMasterAPI.dll</HintPath>
</Reference>
</ItemGroup>
+79 -205
View File
@@ -1,34 +1,21 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using TSMaster;
using TSMasterCAN;
using ZLGCANAPI;
using ZLG_USB_CAN;
namespace XCPCore
{
/// <summary>
/// CAN 传输层后端类型
/// </summary>
public enum CanBackendType
{
/// <summary>同星 TSCAN</summary>
TSCAN,
/// <summary>周立功 ZLGCAN</summary>
ZLGCAN
}
/// <summary>
/// XCP over CAN 传输层实现。
/// 支持同星 CAN(TSMaster)和周立功 CAN(ZLGCAN)两种后端。
/// XCP over CAN 传输层实现(同星 TSMaster 后端)。
/// 命令发送复用 <see cref="CAN.SendMsg"/>(TSMaster 通道报文 API),
/// 接收走 TSMaster 接收 FIFO(tsfifo_receive_canfd_msgs_list / tsfifo_receive_can_msgs_list),
/// 由后台轮询任务按 DTO ID 过滤后推入队列。
/// </summary>
public class XcpCanTransport : IXcpTransport
{
private readonly CanBackendType _backend;
private readonly byte _channel;
private readonly uint _croId;
private readonly uint _dtoId;
@@ -51,23 +38,18 @@ namespace XCPCore
/// <summary>DTO ID(接收响应的 CAN ID)</summary>
public uint DtoId => _dtoId;
/// <summary>CAN 后端类型</summary>
public CanBackendType Backend => _backend;
/// <summary>
/// 创建 CAN XCP 传输层实例
/// 创建 CAN XCP 传输层实例(同星 TSMaster 后端)
/// </summary>
/// <param name="backend">CAN 后端(TSCAN / ZLGCAN)</param>
/// <param name="channel">CAN 通道号</param>
/// <param name="croId">CRO CAN ID(发送命令)</param>
/// <param name="dtoId">DTO CAN ID(接收响应)</param>
/// <param name="isExtended">是否使用扩展帧(29位 ID)</param>
/// <param name="isCanFd">是否使用 CAN FD 帧</param>
/// <param name="timeoutMs">默认超时时间(毫秒)</param>
public XcpCanTransport(CanBackendType backend, byte channel,
public XcpCanTransport(byte channel,
uint croId, uint dtoId, bool isExtended = false, bool isCanFd = false, int timeoutMs = 3000)
{
_backend = backend;
_channel = channel;
_croId = croId;
_dtoId = dtoId;
@@ -77,21 +59,25 @@ namespace XCPCore
}
/// <summary>
/// 打开传输层(启动 DTO 接收轮询任务)
/// 打开传输层(启用 TSMaster 接收 FIFO 并启动 DTO 接收轮询任务)
/// </summary>
public Task<bool> OpenAsync(CancellationToken ct = default)
{
if (IsOpen) return Task.FromResult(true);
// 启用 TSMaster 接收 FIFO(全局开关,幂等)并清空本通道残留帧
TsMasterApi.tsfifo_enable_receive_fifo();
ClearFifoBuffers();
_receiveCts = new CancellationTokenSource();
_receiveTask = Task.Run(() => ReceiveLoop(_receiveCts.Token), _receiveCts.Token);
IsOpen = true;
Debug.WriteLine($"XCP CAN 传输层已打开: Backend={_backend}, CH={_channel}, CRO=0x{_croId:X}, DTO=0x{_dtoId:X}");
Debug.WriteLine($"XCP CAN 传输层已打开: CH={_channel}, CRO=0x{_croId:X}, DTO=0x{_dtoId:X}, FD={_isCanFd}");
return Task.FromResult(true);
}
/// <summary>
/// 发送 CTO 帧(构造 CAN 帧并发送)
/// 发送 CTO 帧(构造 CAN 帧并经 CAN.SendMsg 发送)
/// </summary>
public Task SendCtoAsync(byte[] data, CancellationToken ct = default)
{
@@ -103,30 +89,22 @@ namespace XCPCore
byte[] sendData = new byte[maxLen];
Array.Copy(data, sendData, sendLen);
if (_backend == CanBackendType.TSCAN)
{
// 同星 CAN: 使用 CAN.SendMsg 发送
int result = CAN.SendMsg(
(APP_CHANNEL)_channel,
(int)_croId,
true, // AIsTx
_isExtended, // AIsExt
false, // AIsRemote
(byte)sendLen, // ADLC
sendData,
_isCanFd, // AIsFD
false, // AIsBRS
false, // isUpdateDBCDatabase
0 // sendPeriod (单次发送)
);
int result = CAN.SendMsg(
(APP_CHANNEL)_channel,
(int)_croId,
true, // AIsTx
_isExtended, // AIsExt
false, // AIsRemote
(byte)sendLen, // ADLC
sendData,
_isCanFd, // AIsFD
false, // AIsBRS
false, // isUpdateDBCDatabase
0 // sendPeriod (单次发送)
);
if (result != 0)
throw new InvalidOperationException($"XCP CAN 发送失败: TSCAN 返回值={result}");
}
else // ZLGCAN
{
SendViaZlgCan(sendData, sendLen);
}
if (result != 0)
throw new InvalidOperationException($"XCP CAN 发送失败: TSCAN 返回值={result} ({CAN.GetErrorDescription(result)})");
return Task.CompletedTask;
}
@@ -176,6 +154,7 @@ namespace XCPCore
// 清空队列
while (_responseQueue.TryDequeue(out _)) { }
ClearFifoBuffers();
IsOpen = false;
Debug.WriteLine("XCP CAN 传输层已关闭");
@@ -186,7 +165,18 @@ namespace XCPCore
// ============================================================
/// <summary>
/// 后台接收轮询任务:持续从 CAN 总线读取报文,按 DTO ID 过滤后推入队列
/// 清空 TSMaster 本通道接收 FIFO 残留帧
/// </summary>
private void ClearFifoBuffers()
{
if (_isCanFd)
TsMasterApi.tsfifo_clear_canfd_receive_buffers(_channel);
else
TsMasterApi.tsfifo_clear_can_receive_buffers(_channel);
}
/// <summary>
/// 后台接收轮询任务:从 TSMaster 接收 FIFO 读取报文,按 DTO ID 过滤后推入队列
/// </summary>
private async Task ReceiveLoop(CancellationToken ct)
{
@@ -194,14 +184,10 @@ namespace XCPCore
{
try
{
if (_backend == CanBackendType.TSCAN)
{
ReceiveFromTsCan();
}
if (_isCanFd)
ReceiveFdFromFifo();
else
{
ReceiveFromZlgCan();
}
ReceiveClassicFromFifo();
// 轮询间隔:1ms 足以满足大多数 XCP 场景
await Task.Delay(1, ct).ContinueWith(_ => { }, TaskContinuationOptions.OnlyOnCanceled);
@@ -219,171 +205,59 @@ namespace XCPCore
}
/// <summary>
/// 从同星 CAN 接收队列中读取 DTO 响应帧
/// 从 TSMaster 接收 FIFO 读取 CAN FD 帧并按 DTO ID 过滤
/// </summary>
private void ReceiveFromTsCan()
private void ReceiveFdFromFifo()
{
while (CAN.TryGetReceivedMessage(out var msg))
int count = 0;
if (TsMasterApi.tsfifo_read_canfd_rx_buffer_frame_count(_channel, ref count) != 0 || count <= 0)
return;
var buffers = new TLIBCANFD[count];
int size = count;
if (TsMasterApi.tsfifo_receive_canfd_msgs_list(ref buffers, ref size, _channel, false) != 0)
return;
for (int i = 0; i < size; i++)
{
// 按 DTO ID 过滤
if ((uint)msg.FIdentifier == _dtoId && msg.FIdxChn == _channel)
{
int len = Math.Min((int)msg.FDLC, msg.FData.Length);
var payload = new byte[len];
Array.Copy(msg.FData, payload, len);
_responseQueue.Enqueue(payload);
_responseSignal.Release();
}
EnqueueIfDto(buffers[i].FIdxChn, (uint)buffers[i].FIdentifier, buffers[i].FDLC, buffers[i].FData);
}
}
/// <summary>
/// 从周立功 CAN 接收缓冲区中读取 DTO 响应帧
/// 从 TSMaster 接收 FIFO 读取经典 CAN 帧并按 DTO ID 过滤
/// </summary>
private void ReceiveFromZlgCan()
private void ReceiveClassicFromFifo()
{
IntPtr curChn = ZLG_USBCANFD.chnHandle[ZLG_USBCANFD.channel_index];
if (curChn == IntPtr.Zero) return;
int count = 0;
if (TsMasterApi.tsfifo_read_can_rx_buffer_frame_count(_channel, ref count) != 0 || count <= 0)
return;
if (_isCanFd)
var buffers = new TLIBCAN[count];
int size = count;
if (TsMasterApi.tsfifo_receive_can_msgs_list(ref buffers, ref size, _channel, false) != 0)
return;
for (int i = 0; i < size; i++)
{
uint recNum = ZLGCAN.ZCAN_GetReceiveNum(curChn, 1);
if (recNum == 0) return;
int fdSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_ReceiveFD_Data));
IntPtr ptrData = Marshal.AllocHGlobal(fdSize * (int)Math.Min(recNum, 100));
try
{
uint count = ZLGCAN.ZCAN_ReceiveFD(curChn, ptrData, Math.Min(recNum, 100), 0);
for (int i = 0; i < count; i++)
{
IntPtr curPtr = IntPtr.Add(ptrData, i * fdSize);
var recvData = (ZLGCAN.ZCAN_ReceiveFD_Data)Marshal.PtrToStructure(
curPtr, typeof(ZLGCAN.ZCAN_ReceiveFD_Data))!;
if (recvData.frame.can_id == _dtoId)
{
int len = Math.Min((int)recvData.frame.len, 64);
var payload = new byte[len];
Array.Copy(recvData.frame.data, payload, len);
_responseQueue.Enqueue(payload);
_responseSignal.Release();
}
}
}
finally
{
Marshal.FreeHGlobal(ptrData);
}
}
else
{
uint recNum = ZLGCAN.ZCAN_GetReceiveNum(curChn, 0);
if (recNum == 0) return;
int canSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Receive_Data));
IntPtr ptrData = Marshal.AllocHGlobal(canSize * (int)Math.Min(recNum, 100));
try
{
uint count = ZLGCAN.ZCAN_Receive(curChn, ptrData, Math.Min(recNum, 100), 0);
for (int i = 0; i < count; i++)
{
IntPtr curPtr = IntPtr.Add(ptrData, i * canSize);
var recvData = (ZLGCAN.ZCAN_Receive_Data)Marshal.PtrToStructure(
curPtr, typeof(ZLGCAN.ZCAN_Receive_Data))!;
if (recvData.frame.can_id == _dtoId)
{
int len = Math.Min((int)recvData.frame.can_dlc, 8);
var payload = new byte[len];
Array.Copy(recvData.frame.data, payload, len);
_responseQueue.Enqueue(payload);
_responseSignal.Release();
}
}
}
finally
{
Marshal.FreeHGlobal(ptrData);
}
EnqueueIfDto(buffers[i].FIdxChn, (uint)buffers[i].FIdentifier, buffers[i].FDLC, buffers[i].FData);
}
}
/// <summary>
/// 通过周立功 CAN 发送帧
/// 校验帧(通道 + DTO ID 匹配)后截取有效数据入队并释放等待信号
/// </summary>
private void SendViaZlgCan(byte[] data, int dataLen)
private void EnqueueIfDto(byte frameChannel, uint frameId, byte dlc, byte[] data)
{
IntPtr curChn = ZLG_USBCANFD.chnHandle[ZLG_USBCANFD.channel_index];
if (curChn == IntPtr.Zero)
throw new InvalidOperationException("ZLGCAN 通道句柄无效");
if (frameChannel != _channel || frameId != _dtoId)
return;
if (_isCanFd)
{
var frame = new ZLGCAN.canfd_frame
{
can_id = _croId,
len = (byte)dataLen,
flags = 0,
__res0 = 0,
__res1 = 0,
data = new byte[64]
};
Array.Copy(data, frame.data, dataLen);
var txData = new ZLGCAN.ZCAN_TransmitFD_Data
{
frame = frame,
transmit_type = 0 // 正常发送
};
int size = Marshal.SizeOf(txData);
IntPtr ptr = Marshal.AllocHGlobal(size);
try
{
Marshal.StructureToPtr(txData, ptr, true);
uint ret = ZLGCAN.ZCAN_TransmitFD(curChn, ptr, 1);
if (ret != 1)
throw new InvalidOperationException($"XCP CAN FD 发送失败: ZLGCAN 返回值={ret}");
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
else
{
var frame = new ZLGCAN.can_frame
{
can_id = _croId,
can_dlc = (byte)dataLen,
__pad = 0,
__res0 = 0,
__res1 = 0,
data = new byte[8]
};
Array.Copy(data, frame.data, Math.Min(dataLen, 8));
var txData = new ZLGCAN.ZCAN_Transmit_Data
{
frame = frame,
transmit_type = 0
};
int size = Marshal.SizeOf(txData);
IntPtr ptr = Marshal.AllocHGlobal(size);
try
{
Marshal.StructureToPtr(txData, ptr, true);
uint ret = ZLGCAN.ZCAN_Transmit(curChn, ptr, 1);
if (ret != 1)
throw new InvalidOperationException($"XCP CAN 发送失败: ZLGCAN 返回值={ret}");
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
int len = Math.Min((int)dlc, data?.Length ?? 0);
var payload = new byte[len];
if (len > 0)
Array.Copy(data, payload, len);
_responseQueue.Enqueue(payload);
_responseSignal.Release();
}
}
}