using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; namespace MCUSerialBridgeCLR { /// /// 辅助方法与内部 C 结构体封装,用于 P/Invoke 交互 /// internal static class PortStructHelper { /// /// 将任意结构体序列化为字节数组 /// /// 结构体类型 /// 要序列化的结构体 /// 返回结构体对应的字节数组 /// 使用 Marshal 分配内存并复制内容 public static byte[] StructToBytes(T str) where T : struct { int size = Marshal.SizeOf(); 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; } /// /// 串口端口配置的原生结构体(与 MCU C 层对应) /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct SerialPortConfigC { /// 端口类型(0x01 = Serial) public byte port_type; /// 波特率 public uint baud; /// 接收帧时间间隔 public uint receive_frame_ms; /// 保留字节,填 0 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)] public byte[] reserved; } /// /// CAN 端口配置的原生结构体(与 MCU C 层对应) /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct CANPortConfigC { /// 端口类型(0x02 = CAN) public byte port_type; /// 波特率 public uint baud; /// 最大重发时间 public uint retry_time_ms; /// 保留字节,填 0 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)] public byte[] reserved; } } /// /// MCU 固件版本信息结构体 /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct VersionInfo { /// 产品型号 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] public string ProductionName; /// Git 标签 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)] public string GitTag; /// Git commit 哈希值 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)] public string GitCommit; /// 编译时间(字符串) [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24)] public string BuildTime; /// /// 转换为可读字符串 /// /// 返回包含产品、Tag、Commit、BuildTime 的字符串 public override string ToString() { return $"Product: {ProductionName}, Tag: {GitTag}, Commit: {GitCommit}, Built: {BuildTime}"; } } /// /// MCU 当前运行状态 /// [StructLayout(LayoutKind.Explicit)] public struct MCUState { /// 原始 32 位状态值 [FieldOffset(0)] public uint RawValue; /// 状态子字段 0 [FieldOffset(0)] public byte Substate0; /// 状态子字段 1 [FieldOffset(1)] public byte Substate1; /// 状态子字段 2 [FieldOffset(2)] public byte Substate2; /// 高字节模式标志 [FieldOffset(3)] public byte Mode; /// 是否处于 Bridge 模式 public bool IsBridge => (Mode & 0x80) == 0; /// 是否处于 DIVER 模式 public bool IsDIVER => (Mode & 0x80) != 0; /// /// 返回可读的状态字符串 /// /// 例如 "Bridge: Running" 或 "DIVER: Error" 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}"; } } /// /// 抽象端口配置基类 /// public abstract class PortConfig { /// 端口类型(由子类实现) public abstract byte PortType { get; } /// 序列化端口配置为字节数组(供 P/Invoke 使用) /// 返回固定长度字节数组(16 bytes) public abstract byte[] ToBytes(); } /// /// 串口配置 /// /// /// 构造函数 /// /// 波特率 /// 接收帧间隔 public class SerialPortConfig(uint baud, uint receiveFrameMs) : PortConfig { /// Serial 类型 public override byte PortType => 0x01; /// 波特率 public uint Baud { get; set; } = baud; /// 接收帧间隔 public uint ReceiveFrameMs { get; set; } = receiveFrameMs; /// /// 转换为字节数组 /// /// 16 字节数组 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); } } /// /// CAN 端口配置 /// /// /// 构造函数 /// /// 波特率 /// 重发间隔 public class CANPortConfig(uint baud, uint retryTimeMs) : PortConfig { /// CAN 类型 public override byte PortType => 0x02; /// 波特率 public uint Baud { get; set; } = baud; /// 重发间隔 public uint RetryTimeMs { get; set; } = retryTimeMs; /// /// 转换为字节数组 /// /// 16 字节数组 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); } } /// /// CAN 帧结构(标准帧 11-bit ID + 1-bit RTR + 4-bit DLC + Payload) /// public class CANMessage { /// 标准帧 ID(0~0x7FF,11 位) public ushort ID { get; set; } /// 远程帧标志:false = 数据帧,true = 远程帧 public bool RTR { get; set; } /// 数据长度码:0~8 public byte DLC { get; set; } /// 数据负载,长度必须严格等于 DLC(DLC=0 时可为 null) public byte[] Payload { get; set; } /// /// 序列化为 MCU 协议字节流 /// /// 返回字节数组:2 bytes header + Payload /// 如果 DLC > 8 /// 如果 Payload 长度 != DLC 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; } /// /// 反序列化 MCU 协议字节流为 CANMessage /// /// 原始字节数组 /// 实际有效长度 /// CANMessage 实例 /// 数据长度错误 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(); } return msg; } 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})"; } } /// /// 内部 P/Invoke 层,直接映射 C DLL 函数 /// internal static class MCUSerialBridgeCoreAPI { private const string DLL = @"mcu_serial_bridge.dll"; [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] internal static extern MCUSerialBridgeError msb_open( out IntPtr handle, [MarshalAs(UnmanagedType.LPStr)] string port, uint baud ); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] internal static extern MCUSerialBridgeError msb_close(IntPtr handle); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] internal static extern MCUSerialBridgeError msb_reset(IntPtr handle, uint timeout_ms); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern MCUSerialBridgeError msb_version( IntPtr handle, out VersionInfo version, uint timeout_ms ); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] public static extern MCUSerialBridgeError mcu_state( IntPtr handle, out MCUState state, uint timeout ); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] internal static extern MCUSerialBridgeError msb_configure( IntPtr handle, uint num_ports, IntPtr ports, uint timeout ); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] internal static extern MCUSerialBridgeError msb_read_input( IntPtr handle, [Out] byte[] inputs, uint timeout_ms ); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] internal static extern MCUSerialBridgeError msb_write_output( IntPtr handle, [In] byte[] outputs, uint timeout_ms ); [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)] 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)] 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)] 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)] internal static extern MCUSerialBridgeError msb_write_port( IntPtr handle, byte port_index, [In] byte[] src_data, uint src_data_len, uint timeout_ms ); } /// /// MCU 串口/端口操作托管封装类 /// 实现 IDisposable 管理底层句柄生命周期 /// public class MCUSerialBridge : IDisposable { public static uint MaxPortNumber = 16; private IntPtr nativeHandle = IntPtr.Zero; /// 判断是否已打开 public bool IsOpen => nativeHandle != IntPtr.Zero; /// 构造函数,初始化对象 public MCUSerialBridge() { nativeHandle = IntPtr.Zero; } /// 析构函数 ~MCUSerialBridge() { Dispose(false); } /// 显式释放资源 public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// 内部释放资源方法 /// true 表示手动释放,false 表示析构释放 private void Dispose(bool disposing) { if (nativeHandle != IntPtr.Zero) { MCUSerialBridgeCoreAPI.msb_close(nativeHandle); nativeHandle = IntPtr.Zero; } } /// 打开串口 /// 串口名,如 "COM3" /// 波特率 /// 错误码 public MCUSerialBridgeError Open(string portName, uint baud) { return MCUSerialBridgeCoreAPI.msb_open(out nativeHandle, portName, baud); } /// 关闭串口 /// 错误码 public MCUSerialBridgeError Close() { if (nativeHandle == IntPtr.Zero) return MCUSerialBridgeError.Win_HandleNotFound; MCUSerialBridgeError error = MCUSerialBridgeCoreAPI.msb_close(nativeHandle); nativeHandle = IntPtr.Zero; return error; } /// MCU 复位 /// 错误码 public MCUSerialBridgeError Reset(uint timeout = 200) { if (nativeHandle == IntPtr.Zero) return MCUSerialBridgeError.Win_HandleNotFound; return MCUSerialBridgeCoreAPI.msb_reset(nativeHandle, timeout); } /// 获取固件版本 /// 输出版本信息 /// 超时时间(ms) /// 错误码 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); } /// 获取 MCU 当前状态 /// 输出状态 /// 超时时间(ms) /// 错误码 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); } /// 配置端口 /// 端口集合 /// 超时时间(ms) /// 错误码 public MCUSerialBridgeError Configure(IEnumerable 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); } } /// 读取输入(4 字节) /// 输出数组 /// 超时(ms) /// 错误码 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); } /// 写输出(4 字节) /// 数据数组 /// 超时(ms) /// 错误码 public MCUSerialBridgeError WriteOutput(byte[] outputs, uint timeout = 100) { if (nativeHandle == IntPtr.Zero) return MCUSerialBridgeError.Win_HandleNotFound; return MCUSerialBridgeCoreAPI.msb_write_output(nativeHandle, outputs, timeout); } /// /// 读取 Serial 端口的一帧数据。 /// Serial 上报的数据按帧入队;本接口每次调用最多读取一帧。 /// 注意,如果不及时调用该接口,数据可能会丢失。 /// /// Serial 端口索引 /// 接收到的数据 /// /// 超时时间(毫秒) /// - 0:不等待,有数据立即返回,没有数据立即返回 MSB_Error_NoData /// - >0:若当前无数据,最多等待 timeout,期间有新帧到达则立即返回 /// /// /// 如果已经注册回调,本函数将始终返回 MSB_Error_NoData /// /// /// 错误码 MCUSerialBridgeError /// - OK 成功读取一帧 /// - NoData 当前无可读数据(仅在 timeout == 0 或等待超时) /// - Win_InvalidParam 参数错误 /// public MCUSerialBridgeError ReadSerial(byte portIndex, out byte[] buffer, uint timeout) { buffer = Array.Empty(); 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; } /// /// 写 Serial 端口数据。 /// 注意:对于单个Serial端口,不支持多线程并行发送,不要在上一条数据没有发送完成之前调用该函数,否则有可能导致数据错误。 /// 注意:超时时间一定要大于波特率和数据长度综合得出的帧时间 /// /// Serial 端口索引 /// 待发送数据 /// 超时时间(毫秒) /// /// 错误码 MCUSerialBridgeError /// - OK 成功发送 /// - 其他错误请查看 MCUSerialBridgeError /// 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 ); } /// /// 读取 CAN 端口的一帧数据。 /// CAN 上报的数据按帧入队;本接口每次调用最多读取一帧。 /// 注意,如果不及时调用该接口,数据可能会丢失。 /// /// CAN 端口索引 /// 输出 CAN 消息对象 /// 超时时间(毫秒) /// /// 如果已经注册回调,本函数将始终返回 MSB_Error_NoData /// /// /// 错误码 MCUSerialBridgeError /// - OK 成功读取一帧 /// - NoData 当前无可读数据(仅在 timeout == 0 或等待超时) /// - Win_InvalidParam 参数错误 /// - CAN_DataError CAN数据错误 /// - Win_HandleNotFound 句柄无效 /// 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; } /// /// 写 CAN 端口数据。 /// 注意:CAN 支持多线程发送,最多可同时发送 16 个消息。 /// 但是,多个CAN消息会进入排队队列等待发送,如果消息太多,可能引起超时。 /// /// CAN 端口索引 /// 待发送 CAN 消息对象 /// 超时时间(毫秒),默认 500ms /// /// 错误码 MCUSerialBridgeError /// - OK 成功发送 /// - Win_InvalidParam 参数错误 /// - CAN_DataError CAN 数据错误 /// - Win_HandleNotFound 句柄无效 /// 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 = []; /// /// 注册指定端口(Serial)的回调函数 /// /// 端口索引 /// 接收数据回调,byte[] 为接收到的原始数据 /// 错误码 /// /// 注意事项: /// 1. 回调会在底层 C 层线程中直接调用,请**不要在回调内阻塞**,例如等待 I/O 或 Sleep。 /// 2. 回调内**不能调用 WriteSerial/WriteCAN 等发送函数**,否则可能导致死锁或丢帧。 /// 3. 回调内只能做轻量级操作,例如简单解析、统计或打标记。 /// 4. 若需要复杂处理(例如长时间解析、解码、存储数据库等),请**将数据入队到另一个线程**,再在后台处理。 /// 5. 数据可能随时到来,请保证回调尽快返回,避免影响后续帧接收。 /// 6. 不要把其他类型的端口注册到这个接口,接口不对 portIndex 做类型检查。 /// public MCUSerialBridgeError RegisterSerialPortCallback( byte portIndex, Action callback ) { if (callback == null) return MCUSerialBridgeError.Win_InvalidParam; if (portIndex > MaxPortNumber) return MCUSerialBridgeError.Config_PortNumOver; // 包装 C# 回调为 P/Invoke 委托 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 ); } /// /// 注册指定端口(CAN)的回调函数 /// /// 端口索引 /// 接收数据回调,CANMessage 为接收到的原始数据 /// 错误码 /// /// 注意事项: /// 1. 回调会在底层 C 层线程中直接调用,请**不要在回调内阻塞**,例如等待 I/O 或 Sleep。 /// 2. 回调内**不能调用 WriteSerial/WriteCAN 等发送函数**,否则可能导致死锁或丢帧。 /// 3. 回调内只能做轻量级操作,例如简单解析、统计或打标记。 /// 4. 若需要复杂处理(例如长时间解析、解码、存储数据库等),请**将数据入队到另一个线程**,再在后台处理。 /// 5. 数据可能随时到来,请保证回调尽快返回,避免影响后续帧接收。 /// 6. 不要把其他类型的端口注册到这个接口,接口不对 portIndex 做类型检查。 /// public MCUSerialBridgeError RegisterCANPortCallback( byte portIndex, Action callback ) { if (callback == null) return MCUSerialBridgeError.Win_InvalidParam; if (portIndex > MaxPortNumber) return MCUSerialBridgeError.Config_PortNumOver; // 包装 C# 回调为 P/Invoke 委托 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 ); } } }