Files
ParkingRobot/MedullaAdapter/MCUSerialBridgeCLR.cs
T

929 lines
34 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.
// C#调用MCU通信桥
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace MCUSerialBridgeCLR
{
/// <summary>
/// 辅助方法与内部 C 结构体封装,用于 P/Invoke 交互
/// </summary>
internal static class PortStructHelper
{
/// <summary>
/// 将任意结构体序列化为字节数组
/// </summary>
/// <typeparam name="T">结构体类型</typeparam>
/// <param name="str">要序列化的结构体</param>
/// <returns>返回结构体对应的字节数组</returns>
/// <remarks>使用 Marshal 分配内存并复制内容</remarks>
public static byte[] StructToBytes<T>(T str)
where T : struct
{
int size = Marshal.SizeOf<T>();
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
try
{
Marshal.StructureToPtr(str, ptr, false);
Marshal.Copy(ptr, arr, 0, size);
}
finally
{
Marshal.FreeHGlobal(ptr);
}
return arr;
}
/// <summary>
/// 串口端口配置的原生结构体(与 MCU C 层对应)
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SerialPortConfigC
{
/// <summary>端口类型(0x01 = Serial</summary>
public byte port_type;
/// <summary>波特率</summary>
public uint baud;
/// <summary>接收帧时间间隔</summary>
public uint receive_frame_ms;
/// <summary>保留字节,填 0</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
public byte[] reserved;
}
/// <summary>
/// CAN 端口配置的原生结构体(与 MCU C 层对应)
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CANPortConfigC
{
/// <summary>端口类型(0x02 = CAN</summary>
public byte port_type;
/// <summary>波特率</summary>
public uint baud;
/// <summary>最大重发时间</summary>
public uint retry_time_ms;
/// <summary>保留字节,填 0</summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
public byte[] reserved;
}
}
/// <summary>
/// MCU 固件版本信息结构体
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct VersionInfo
{
/// <summary>产品型号</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
public string ProductionName;
/// <summary>Git 标签</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
public string GitTag;
/// <summary>Git commit 哈希值</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
public string GitCommit;
/// <summary>编译时间(字符串)</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)]
public string BuildTime;
/// <summary>
/// 转换为可读字符串
/// </summary>
/// <returns>返回包含产品、Tag、Commit、BuildTime 的字符串</returns>
// M层MCU适配:格式化固件版本信息便于日志显示。
public override string ToString()
{
return $"Product: {ProductionName}, Tag: {GitTag}, Commit: {GitCommit}, Built: {BuildTime}";
}
}
/// <summary>
/// MCU 当前运行状态
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct MCUState
{
/// <summary>原始 32 位状态值</summary>
[FieldOffset(0)]
public uint RawValue;
/// <summary>状态子字段 0</summary>
[FieldOffset(0)]
public byte Substate0;
/// <summary>状态子字段 1</summary>
[FieldOffset(1)]
public byte Substate1;
/// <summary>状态子字段 2</summary>
[FieldOffset(2)]
public byte Substate2;
/// <summary>高字节模式标志</summary>
[FieldOffset(3)]
public byte Mode;
/// <summary>是否处于 Bridge 模式</summary>
public bool IsBridge => (Mode & 0x80) == 0;
/// <summary>是否处于 DIVER 模式</summary>
public bool IsDIVER => (Mode & 0x80) != 0;
/// <summary>
/// 返回可读的状态字符串
/// </summary>
/// <returns>例如 "Bridge: Running" 或 "DIVER: Error"</returns>
// M层MCU适配:格式化MCU运行状态便于日志显示。
public override string ToString()
{
string modeStr = IsBridge ? "Bridge" : "DIVER";
uint substate = (uint)(Substate0 | (Substate1 << 8) | (Substate2 << 16));
string subStr = substate switch
{
0x00000000 => "Idle",
0x0000000F => "Running",
0x000000FF => "Error",
0x00000001 => "Configured", // DIVER specific
0x8000000F => "Running",
0x800000FF => "Error",
_ => $"Unknown (0x{substate:X6})",
};
return $"{modeStr}: {subStr}";
}
}
/// <summary>
/// 抽象端口配置基类
/// </summary>
public abstract class PortConfig
{
/// <summary>端口类型(由子类实现)</summary>
public abstract byte PortType { get; }
/// <summary>序列化端口配置为字节数组(供 P/Invoke 使用)</summary>
/// <returns>返回固定长度字节数组(16 bytes</returns>
// M层MCU适配:将端口配置序列化为原生接口字节。
public abstract byte[] ToBytes();
}
/// <summary>
/// 串口配置
/// </summary>
/// <remarks>
/// 构造函数
/// </remarks>
/// <param name="baud">波特率</param>
/// <param name="receiveFrameMs">接收帧间隔</param>
// M层MCU适配:创建串口通信参数配置。
public class SerialPortConfig(uint baud, uint receiveFrameMs) : PortConfig
{
/// <summary>Serial 类型</summary>
public override byte PortType => 0x01;
/// <summary>波特率</summary>
public uint Baud { get; set; } = baud;
/// <summary>接收帧间隔</summary>
public uint ReceiveFrameMs { get; set; } = receiveFrameMs;
/// <summary>
/// 转换为字节数组
/// </summary>
/// <returns>16 字节数组</returns>
// M层MCU适配:序列化串口波特率和组帧时间。
public override byte[] ToBytes()
{
var c = new PortStructHelper.SerialPortConfigC
{
port_type = PortType,
baud = Baud,
receive_frame_ms = ReceiveFrameMs,
reserved = new byte[7],
};
return PortStructHelper.StructToBytes(c);
}
}
/// <summary>
/// CAN 端口配置
/// </summary>
/// <remarks>
/// 构造函数
/// </remarks>
/// <param name="baud">波特率</param>
/// <param name="retryTimeMs">重发间隔</param>
// M层MCU适配:创建CAN通信参数配置。
public class CANPortConfig(uint baud, uint retryTimeMs) : PortConfig
{
/// <summary>CAN 类型</summary>
public override byte PortType => 0x02;
/// <summary>波特率</summary>
public uint Baud { get; set; } = baud;
/// <summary>重发间隔</summary>
public uint RetryTimeMs { get; set; } = retryTimeMs;
/// <summary>
/// 转换为字节数组
/// </summary>
/// <returns>16 字节数组</returns>
// M层MCU适配:序列化CAN波特率和重试时间。
public override byte[] ToBytes()
{
var c = new PortStructHelper.CANPortConfigC
{
port_type = PortType,
baud = Baud,
retry_time_ms = RetryTimeMs,
reserved = new byte[7],
};
return PortStructHelper.StructToBytes(c);
}
}
/// <summary>
/// CAN 帧结构(标准帧 11-bit ID + 1-bit RTR + 4-bit DLC + Payload
/// </summary>
public class CANMessage
{
/// <summary>标准帧 ID0~0x7FF11 位)</summary>
public ushort ID { get; set; }
/// <summary>远程帧标志:false = 数据帧,true = 远程帧</summary>
public bool RTR { get; set; }
/// <summary>数据长度码:0~8</summary>
public byte DLC { get; set; }
/// <summary>数据负载,长度必须严格等于 DLCDLC=0 时可为 null</summary>
public byte[] Payload { get; set; }
/// <summary>
/// 序列化为 MCU 协议字节流
/// </summary>
/// <returns>返回字节数组:2 bytes header + Payload</returns>
/// <exception cref="ArgumentOutOfRangeException">如果 DLC > 8</exception>
/// <exception cref="ArgumentException">如果 Payload 长度 != DLC</exception>
// M层CAN适配:将标准或扩展CAN帧序列化为原生布局。
public byte[] ToBytes()
{
if (DLC > 8)
throw new ArgumentOutOfRangeException(nameof(DLC), "DLC must be 0-8");
if (DLC > 0 && (Payload == null || Payload.Length != DLC))
throw new ArgumentException("Payload length must equal DLC");
// 构造 2 字节 header
ushort header = 0;
header |= (ushort)(ID & 0x7FF); // bits 0-10
if (RTR)
header |= (1 << 11); // bit 11
header |= (ushort)((DLC & 0xF) << 12); // bits 12-15
byte[] result = new byte[2 + DLC];
byte[] headerBytes = BitConverter.GetBytes(header); // 小端序
result[0] = headerBytes[0];
result[1] = headerBytes[1];
if (DLC > 0)
Buffer.BlockCopy(Payload, 0, result, 2, DLC);
return result;
}
/// <summary>
/// 反序列化 MCU 协议字节流为 CANMessage
/// </summary>
/// <param name="data">原始字节数组</param>
/// <param name="length">实际有效长度</param>
/// <returns>CANMessage 实例</returns>
/// <exception cref="ArgumentException">数据长度错误</exception>
// M层CAN适配:从原生缓冲区还原CAN消息。
public static CANMessage FromBytes(byte[] data, uint length)
{
if (data == null || length > data.Length || length < 2)
throw new ArgumentException("Data must be at least 2 bytes");
ushort header = BitConverter.ToUInt16(data, 0);
var msg = new CANMessage
{
ID = (ushort)(header & 0x7FF),
RTR = (header & (1 << 11)) != 0,
DLC = (byte)((header >> 12) & 0xF),
};
if (msg.DLC > 0)
{
if (length < 2 + msg.DLC)
throw new ArgumentException("Data length less than DLC");
msg.Payload = new byte[msg.DLC];
Buffer.BlockCopy(data, 2, msg.Payload, 0, msg.DLC);
}
else
{
msg.Payload = Array.Empty<byte>();
}
return msg;
}
// M层CAN诊断:格式化CAN标识符和数据内容。
public override string ToString()
{
string payloadStr =
(Payload == null || Payload.Length == 0)
? "[]"
: "0x[" + string.Join(" ", Payload.Select(b => $"{b:X2}")) + "]";
return $"CANMessage(ID=0x{ID:X3}, RTR={RTR}, DLC={DLC}, Payload={payloadStr})";
}
}
/// <summary>
/// 内部 P/Invoke 层,直接映射 C DLL 函数
/// </summary>
internal static class MCUSerialBridgeCoreAPI
{
private const string DLL = @"mcu_serial_bridge.dll";
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:打开MCU串口桥设备。
internal static extern MCUSerialBridgeError msb_open(
out IntPtr handle,
[MarshalAs(UnmanagedType.LPStr)] string port,
uint baud
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:关闭MCU串口桥设备。
internal static extern MCUSerialBridgeError msb_close(IntPtr handle);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:复位MCU串口桥。
internal static extern MCUSerialBridgeError msb_reset(IntPtr handle, uint timeout_ms);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:读取MCU固件版本。
public static extern MCUSerialBridgeError msb_version(
IntPtr handle,
out VersionInfo version,
uint timeout_ms
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:读取MCU当前运行状态。
public static extern MCUSerialBridgeError mcu_state(
IntPtr handle,
out MCUState state,
uint timeout
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:下发串口桥端口配置。
internal static extern MCUSerialBridgeError msb_configure(
IntPtr handle,
uint num_ports,
IntPtr ports,
uint timeout
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:读取MCU数字输入。
internal static extern MCUSerialBridgeError msb_read_input(
IntPtr handle,
[Out] byte[] inputs,
uint timeout_ms
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:写入MCU数字输出。
internal static extern MCUSerialBridgeError msb_write_output(
IntPtr handle,
[In] byte[] outputs,
uint timeout_ms
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:从指定串口或CAN端口读取数据。
internal static extern MCUSerialBridgeError msb_read_port(
IntPtr handle,
byte port_index,
[Out] byte[] dst_data,
uint dst_capacity,
out uint out_length,
uint timeout_ms
);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
// M层硬件桥接:定义串口或CAN端口收到原生数据时的回调签名。
internal delegate void msb_on_port_data_callback_function_t(
IntPtr dst_data,
uint dst_data_size,
IntPtr user_ctx
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:注册端口数据到达回调。
internal static extern MCUSerialBridgeError msb_register_port_data_callback(
IntPtr handle,
byte port_index,
msb_on_port_data_callback_function_t callback,
IntPtr user_ctx
);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
// M层原生接口:向指定串口或CAN端口写入数据。
internal static extern MCUSerialBridgeError msb_write_port(
IntPtr handle,
byte port_index,
[In] byte[] src_data,
uint src_data_len,
uint timeout_ms
);
}
/// <summary>
/// MCU 串口/端口操作托管封装类
/// 实现 IDisposable 管理底层句柄生命周期
/// </summary>
public class MCUSerialBridge : IDisposable
{
public static uint MaxPortNumber = 16;
private IntPtr nativeHandle = IntPtr.Zero;
/// <summary>判断是否已打开</summary>
public bool IsOpen => nativeHandle != IntPtr.Zero;
/// <summary>构造函数,初始化对象</summary>
// M层MCU适配:创建串口桥包装器并固定原生回调委托。
public MCUSerialBridge()
{
nativeHandle = IntPtr.Zero;
}
/// <summary>析构函数</summary>
// M层MCU适配:对象回收时兜底释放原生串口桥句柄。
~MCUSerialBridge()
{
Dispose(false);
}
/// <summary>显式释放资源</summary>
// M层MCU适配:释放串口桥句柄和非托管资源。
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>内部释放资源方法</summary>
/// <param name="disposing">true 表示手动释放,false 表示析构释放</param>
// M层MCU适配:按托管或终结路径关闭原生句柄。
private void Dispose(bool disposing)
{
if (nativeHandle != IntPtr.Zero)
{
MCUSerialBridgeCoreAPI.msb_close(nativeHandle);
nativeHandle = IntPtr.Zero;
}
}
/// <summary>打开串口</summary>
/// <param name="portName">串口名,如 "COM3"</param>
/// <param name="baud">波特率</param>
/// <returns>错误码</returns>
// M层单车通信:按端口名和波特率连接MCU串口桥。
public MCUSerialBridgeError Open(string portName, uint baud)
{
return MCUSerialBridgeCoreAPI.msb_open(out nativeHandle, portName, baud);
}
/// <summary>关闭串口</summary>
/// <returns>错误码</returns>
// M层单车通信:关闭当前MCU串口桥连接。
public MCUSerialBridgeError Close()
{
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
MCUSerialBridgeError error = MCUSerialBridgeCoreAPI.msb_close(nativeHandle);
nativeHandle = IntPtr.Zero;
return error;
}
/// <summary>MCU 复位</summary>
/// <returns>错误码</returns>
// M层单车通信:请求MCU复位并等待结果。
public MCUSerialBridgeError Reset(uint timeout = 200)
{
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
return MCUSerialBridgeCoreAPI.msb_reset(nativeHandle, timeout);
}
/// <summary>获取固件版本</summary>
/// <param name="version">输出版本信息</param>
/// <param name="timeout">超时时间(ms</param>
/// <returns>错误码</returns>
// M层MCU诊断:读取串口桥固件版本。
public MCUSerialBridgeError GetVersion(out VersionInfo version, uint timeout = 200)
{
version = new VersionInfo();
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
return MCUSerialBridgeCoreAPI.msb_version(nativeHandle, out version, timeout);
}
/// <summary>获取 MCU 当前状态</summary>
/// <param name="state">输出状态</param>
/// <param name="timeout">超时时间(ms</param>
/// <returns>错误码</returns>
// M层MCU诊断:读取串口桥运行状态。
public MCUSerialBridgeError GetState(out MCUState state, uint timeout = 200)
{
state = new MCUState();
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
return MCUSerialBridgeCoreAPI.mcu_state(nativeHandle, out state, timeout);
}
/// <summary>配置端口</summary>
/// <param name="ports">端口集合</param>
/// <param name="timeout">超时时间(ms</param>
/// <returns>错误码</returns>
// M层MCU适配:批量配置CAN和串口通道参数。
public MCUSerialBridgeError Configure(IEnumerable<PortConfig> ports, uint timeout = 200)
{
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
if (ports == null)
return MCUSerialBridgeError.Win_InvalidParam;
// 转换成数组
PortConfig[] portArray = ports as PortConfig[] ?? ports.ToArray();
int count = portArray.Length;
// 分配连续原生内存
int structSize = 16; // 每个 PortConfig 固定 16 字节
IntPtr nativePorts = Marshal.AllocHGlobal(structSize * count);
try
{
for (int i = 0; i < count; i++)
{
byte[] bytes = portArray[i].ToBytes();
if (bytes.Length != structSize)
return MCUSerialBridgeError.Win_InvalidParam;
Marshal.Copy(bytes, 0, nativePorts + i * structSize, structSize);
}
// 调用底层 API
return MCUSerialBridgeCoreAPI.msb_configure(
nativeHandle,
(uint)count,
nativePorts,
timeout
);
}
catch
{
return MCUSerialBridgeError.Win_InvalidParam;
}
finally
{
Marshal.FreeHGlobal(nativePorts);
}
}
/// <summary>读取输入(4 字节)</summary>
/// <param name="inputs">输出数组</param>
/// <param name="timeout">超时(ms</param>
/// <returns>错误码</returns>
// M层单车IO:读取MCU数字输入状态。
public MCUSerialBridgeError ReadInput(out byte[] inputs, uint timeout = 100)
{
inputs = new byte[4];
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
return MCUSerialBridgeCoreAPI.msb_read_input(nativeHandle, inputs, timeout);
}
/// <summary>写输出(4 字节)</summary>
/// <param name="outputs">数据数组</param>
/// <param name="timeout">超时(ms</param>
/// <returns>错误码</returns>
// M层单车IO:写入继电器、灯光等数字输出状态。
public MCUSerialBridgeError WriteOutput(byte[] outputs, uint timeout = 100)
{
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
return MCUSerialBridgeCoreAPI.msb_write_output(nativeHandle, outputs, timeout);
}
/// <summary>
/// 读取 Serial 端口的一帧数据。
/// Serial 上报的数据按帧入队;本接口每次调用最多读取一帧。
/// 注意,如果不及时调用该接口,数据可能会丢失。
/// </summary>
/// <param name="portIndex">Serial 端口索引</param>
/// <param name="buffer">接收到的数据</param>
/// <param name="timeout">
/// 超时时间(毫秒)
/// - 0:不等待,有数据立即返回,没有数据立即返回 MSB_Error_NoData
/// - >0:若当前无数据,最多等待 timeout,期间有新帧到达则立即返回
/// </param>
/// <remarks>
/// 如果已经注册回调,本函数将始终返回 MSB_Error_NoData
/// </remarks>
/// <returns>
/// 错误码 MCUSerialBridgeError
/// - OK 成功读取一帧
/// - NoData 当前无可读数据(仅在 timeout == 0 或等待超时)
/// - Win_InvalidParam 参数错误
/// </returns>
// M层串口通信:同步读取指定MCU串口的数据。
public MCUSerialBridgeError ReadSerial(byte portIndex, out byte[] buffer, uint timeout)
{
buffer = Array.Empty<byte>();
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
const int MAX_PORT_FRAME = 2048;
byte[] tmp = new byte[MAX_PORT_FRAME];
var err = MCUSerialBridgeCoreAPI.msb_read_port(
nativeHandle,
portIndex,
tmp,
(uint)tmp.Length,
out uint outLen,
timeout
);
if (err != MCUSerialBridgeError.OK)
return err;
buffer = new byte[outLen];
Buffer.BlockCopy(tmp, 0, buffer, 0, (int)outLen);
return MCUSerialBridgeError.OK;
}
/// <summary>
/// 写 Serial 端口数据。
/// 注意:对于单个Serial端口,不支持多线程并行发送,不要在上一条数据没有发送完成之前调用该函数,否则有可能导致数据错误。
/// 注意:超时时间一定要大于波特率和数据长度综合得出的帧时间
/// </summary>
/// <param name="portIndex">Serial 端口索引</param>
/// <param name="data">待发送数据</param>
/// <param name="timeout">超时时间(毫秒)</param>
/// <returns>
/// 错误码 MCUSerialBridgeError
/// - OK 成功发送
/// - 其他错误请查看 MCUSerialBridgeError
/// </returns>
// M层串口通信:向指定MCU串口发送数据。
public MCUSerialBridgeError WriteSerial(byte portIndex, byte[] data, uint timeout)
{
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
if (data == null || data.Length == 0)
return MCUSerialBridgeError.Win_InvalidParam;
return MCUSerialBridgeCoreAPI.msb_write_port(
nativeHandle,
portIndex,
data,
(uint)data.Length,
timeout
);
}
/// <summary>
/// 读取 CAN 端口的一帧数据。
/// CAN 上报的数据按帧入队;本接口每次调用最多读取一帧。
/// 注意,如果不及时调用该接口,数据可能会丢失。
/// </summary>
/// <param name="portIndex">CAN 端口索引</param>
/// <param name="message">输出 CAN 消息对象</param>
/// <param name="timeout">超时时间(毫秒)</param>
/// <remarks>
/// 如果已经注册回调,本函数将始终返回 MSB_Error_NoData
/// </remarks>
/// <returns>
/// 错误码 MCUSerialBridgeError
/// - OK 成功读取一帧
/// - NoData 当前无可读数据(仅在 timeout == 0 或等待超时)
/// - Win_InvalidParam 参数错误
/// - CAN_DataError CAN数据错误
/// - Win_HandleNotFound 句柄无效
/// </returns>
// M层CAN通信:同步读取指定CAN通道的一帧消息。
public MCUSerialBridgeError ReadCAN(byte portIndex, out CANMessage message, uint timeout)
{
message = null;
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
const int MAX_FRAME = 16;
byte[] tmp = new byte[MAX_FRAME];
var err = MCUSerialBridgeCoreAPI.msb_read_port(
nativeHandle,
portIndex,
tmp,
(uint)tmp.Length,
out uint outLen,
timeout
);
if (err != MCUSerialBridgeError.OK)
return err;
try
{
message = CANMessage.FromBytes(tmp, outLen);
}
catch
{
return MCUSerialBridgeError.CAN_DataError;
}
return MCUSerialBridgeError.OK;
}
/// <summary>
/// 写 CAN 端口数据。
/// 注意:CAN 支持多线程发送,最多可同时发送 16 个消息。
/// 但是,多个CAN消息会进入排队队列等待发送,如果消息太多,可能引起超时。
/// </summary>
/// <param name="portIndex">CAN 端口索引</param>
/// <param name="message">待发送 CAN 消息对象</param>
/// <param name="timeout">超时时间(毫秒),默认 500ms</param>
/// <returns>
/// 错误码 MCUSerialBridgeError
/// - OK 成功发送
/// - Win_InvalidParam 参数错误
/// - CAN_DataError CAN 数据错误
/// - Win_HandleNotFound 句柄无效
/// </returns>
// M层CAN通信:向指定CAN通道发送一帧消息。
public MCUSerialBridgeError WriteCAN(byte portIndex, CANMessage message, uint timeout)
{
if (nativeHandle == IntPtr.Zero)
return MCUSerialBridgeError.Win_HandleNotFound;
if (message == null)
return MCUSerialBridgeError.Win_InvalidParam;
try
{
byte[] buffer = message.ToBytes();
return MCUSerialBridgeCoreAPI.msb_write_port(
nativeHandle,
portIndex,
buffer,
(uint)buffer.Length,
timeout
);
}
catch
{
return MCUSerialBridgeError.CAN_DataError;
}
}
private readonly Dictionary<
byte,
MCUSerialBridgeCoreAPI.msb_on_port_data_callback_function_t
> _portCallbacks = [];
/// <summary>
/// 注册指定端口(Serial)的回调函数
/// </summary>
/// <param name="portIndex">端口索引</param>
/// <param name="callback">接收数据回调,byte[] 为接收到的原始数据</param>
/// <returns>错误码</returns>
/// <remarks>
/// 注意事项:
/// 1. 回调会在底层 C 层线程中直接调用,请**不要在回调内阻塞**,例如等待 I/O 或 Sleep。
/// 2. 回调内**不能调用 WriteSerial/WriteCAN 等发送函数**,否则可能导致死锁或丢帧。
/// 3. 回调内只能做轻量级操作,例如简单解析、统计或打标记。
/// 4. 若需要复杂处理(例如长时间解析、解码、存储数据库等),请**将数据入队到另一个线程**,再在后台处理。
/// 5. 数据可能随时到来,请保证回调尽快返回,避免影响后续帧接收。
/// 6. 不要把其他类型的端口注册到这个接口,接口不对 portIndex 做类型检查。
/// </remarks>
// M层串口通信:注册指定串口的异步接收回调。
public MCUSerialBridgeError RegisterSerialPortCallback(
byte portIndex,
Action<byte[]> callback
)
{
if (callback == null)
return MCUSerialBridgeError.Win_InvalidParam;
if (portIndex > MaxPortNumber)
return MCUSerialBridgeError.Config_PortNumOver;
// 包装 C# 回调为 P/Invoke 委托
// M层串口回调:复制原生缓存并转交托管回调处理。
void del(IntPtr dst_data, uint dst_data_size, IntPtr user_ctx)
{
byte[] data = new byte[dst_data_size];
Marshal.Copy(dst_data, data, 0, (int)dst_data_size);
callback(data);
}
// 保存引用,防止 GC 回收
_portCallbacks[portIndex] = del;
// 调用 C 层注册
return MCUSerialBridgeCoreAPI.msb_register_port_data_callback(
nativeHandle,
portIndex,
_portCallbacks[portIndex],
IntPtr.Zero
);
}
/// <summary>
/// 注册指定端口(CAN)的回调函数
/// </summary>
/// <param name="portIndex">端口索引</param>
/// <param name="callback">接收数据回调,CANMessage 为接收到的原始数据</param>
/// <returns>错误码</returns>
/// <remarks>
/// 注意事项:
/// 1. 回调会在底层 C 层线程中直接调用,请**不要在回调内阻塞**,例如等待 I/O 或 Sleep。
/// 2. 回调内**不能调用 WriteSerial/WriteCAN 等发送函数**,否则可能导致死锁或丢帧。
/// 3. 回调内只能做轻量级操作,例如简单解析、统计或打标记。
/// 4. 若需要复杂处理(例如长时间解析、解码、存储数据库等),请**将数据入队到另一个线程**,再在后台处理。
/// 5. 数据可能随时到来,请保证回调尽快返回,避免影响后续帧接收。
/// 6. 不要把其他类型的端口注册到这个接口,接口不对 portIndex 做类型检查。
/// </remarks>
// M层CAN通信:注册指定CAN通道的异步接收回调。
public MCUSerialBridgeError RegisterCANPortCallback(
byte portIndex,
Action<CANMessage> callback
)
{
if (callback == null)
return MCUSerialBridgeError.Win_InvalidParam;
if (portIndex > MaxPortNumber)
return MCUSerialBridgeError.Config_PortNumOver;
// 包装 C# 回调为 P/Invoke 委托
// M层CAN回调:还原原生CAN帧并转交托管回调处理。
void del(IntPtr dst_data, uint dst_data_size, IntPtr user_ctx)
{
try
{
byte[] data = new byte[dst_data_size];
Marshal.Copy(dst_data, data, 0, (int)dst_data_size);
CANMessage msg = CANMessage.FromBytes(data, dst_data_size);
callback(msg);
}
catch
{
// 解析失败直接忽略,保证回调不会抛异常阻塞 C 层线程
}
}
// 保存引用,防止 GC 回收
_portCallbacks[portIndex] = del;
// 调用 C 层注册
return MCUSerialBridgeCoreAPI.msb_register_port_data_callback(
nativeHandle,
portIndex,
_portCallbacks[portIndex],
IntPtr.Zero
);
}
}
}