From 0e551a928565266d12977c09003a77af25c97335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AD=B1=E5=82=91?= <840465812@qq.com> Date: Mon, 6 May 2019 15:38:11 +0800 Subject: [PATCH] Add USB CAN Helper files --- CANHelper/ControlCAN.dll | Bin 0 -> 16896 bytes CANHelper/USBCAN/CANHelper.cs | 346 +++++++++++++++++++++++++++++ CANHelper/USBCAN/CAN_API.cs | 262 ++++++++++++++++++++++ CANHelper/kerneldlls/kerneldll.ini | 28 +++ CANHelper/kerneldlls/usbcan.dll | Bin 0 -> 26112 bytes 5 files changed, 636 insertions(+) create mode 100644 CANHelper/ControlCAN.dll create mode 100644 CANHelper/USBCAN/CANHelper.cs create mode 100644 CANHelper/USBCAN/CAN_API.cs create mode 100644 CANHelper/kerneldlls/kerneldll.ini create mode 100644 CANHelper/kerneldlls/usbcan.dll diff --git a/CANHelper/ControlCAN.dll b/CANHelper/ControlCAN.dll new file mode 100644 index 0000000000000000000000000000000000000000..daf472ad5a47ff2d310483772b15ec6b8a5b6848 GIT binary patch literal 16896 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 O!2J06cmysn0{;rgf^Y-? literal 0 HcmV?d00001 diff --git a/CANHelper/USBCAN/CANHelper.cs b/CANHelper/USBCAN/CANHelper.cs new file mode 100644 index 0000000..4e0bf77 --- /dev/null +++ b/CANHelper/USBCAN/CANHelper.cs @@ -0,0 +1,346 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; + +namespace USBCAN +{ + /// + /// CAN帮助类 + /// + /// + /// 使用方法: + /// + /// CANHelper.Instance.Initialize(); // 在首次使用时需要初始化,初始化失败时会抛出异常 + /// CANHelper.Instance.ConsumptionFrameEvent += (CAN_API.VCI_CAN_OBJ frame){ /* TODO:在这里消费掉数据帧,注意,这里是异步调用 */ } + /// CANHelper.Instance.SendData(0x1111, data); // 向CAN发送数据,具体方法参阅注释 + /// + /// + public sealed class CANHelper : IDisposable + { + /// + /// CAN实例 + /// + public static readonly CANHelper Instance = new CANHelper(); + + #region 公开属性 + /// + /// CAN是否打开 + /// + public bool IsOpen { private set; get; } = false; + #endregion + + #region 释放 + /// + /// Flag: 标识Disposed是否已经被调用 + /// + private bool _IsDisposed = false; + + /// + /// 公开的Dispose方法 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 实际Dispose方法 + /// + /// 是否由用户调用 + private void Dispose(bool disposing) + { + if (_IsDisposed) + return; + + if (disposing) + { + // 释放托管成员 + } + + // 释放非托管成员 + + // 关闭CAN设备 + CloseDevice(); + + // 标识为已执行Dispose + _IsDisposed = true; + } + + /// + /// 析构 + /// + ~CANHelper() + { + // 释放非托管内存 + Dispose(false); + } + #endregion + + #region 打开与关闭 + /// + /// 初始化并打开CAN设备 + /// + public void Initialize() + { + // 如果已经打开,直接返回 + if (IsOpen) + return; + + // 打开设备 + if (CAN_API.VCI_OpenDevice(_DeviceType, _DeviceInd, _CANInd) == CAN_API.STATUS_ERR) + throw new Exception("CAN设备打开失败,错误信息:" + ReadErrorMessage()); + + // 构造CAN配置信息 + CAN_API.VCI_INIT_CONFIG pInitConfig = new CAN_API.VCI_INIT_CONFIG + { + AccCode = 0x00000000, // 表示全部接收(全部接收: AccCode:0x00000000) + AccMask = 0xFFFFFFFF, // ( AccMask:0xFFFFFFFF) + Reserved = 0x00, // 保留,填0 + Filter = 0x01, // 滤波方式 01 + Timing0 = 0x01, // ( 相当于波特率1000kbps ) + Timing1 = 0x14, // ( 相当于波特率1000kbps ) + Mode = 0x00 // 正常模式; 0:正常模式,可以IO。 1:表示只听模式(只接收,不影响总线) + }; + // 初始化CAN + if (CAN_API.VCI_InitCAN(_DeviceType, _DeviceInd, _CANInd, ref pInitConfig) == CAN_API.STATUS_ERR) + throw new Exception("CAN初始化失败,错误信息:" + ReadErrorMessage()); + + // 启动CAN + if (CAN_API.VCI_StartCAN(_DeviceType, _DeviceInd, _CANInd) == CAN_API.STATUS_ERR) + throw new Exception("CAN启动失败,错误信息:" + ReadErrorMessage()); + + // 若未出错,标识打开 + IsOpen = true; + + // 初始化消息帧缓冲区,上限为128帧报文,若满了还未消费则阻塞 + _FrameBuffer = new BlockingCollection(128); + + // 生产者消费者开始工作 + StartWork(); + } + + /// + /// 关闭CAN设备 + /// + public void CloseDevice() + { + if (IsOpen) + { + // 关闭设备 + if (CAN_API.VCI_CloseDevice(_DeviceType, _DeviceInd) == CAN_API.STATUS_ERR) + throw new Exception("CAN设备关闭失败,错误信息:" + ReadErrorMessage()); + + IsOpen = false; + } + } + #endregion + + #region 公开方法 + /// + /// 读取错误信息 + /// + /// + public string ReadErrorMessage() + { + CAN_API.VCI_ERR_INFO errInfo = new CAN_API.VCI_ERR_INFO(); + try + { + // 尝试读取错误信息 + if (CAN_API.VCI_ReadErrInfo(_DeviceType, _DeviceInd, _CANInd, ref errInfo) == CAN_API.STATUS_ERR) + return "读取错误信息失败"; + } + catch (Exception ex) + { + return string.Format("读取错误信息时发生异常({0})", ex.Message); + } + + if (errInfo.ErrCode == 0x00) + { + // 若无错误信息,则返回‘无错误信息’ + return "无错误信息"; + } + + // 由于可能同时出现多种错误,使用按位与的方式读取错误信息 + List errMsgList = new List(); + if ((errInfo.ErrCode & (uint)CAN_API.ErrorType.ERR_CAN_OVERFLOW) != 0) + errMsgList.Add("CAN控制器内部FIFO溢出"); + if ((errInfo.ErrCode & (uint)CAN_API.ErrorType.ERR_CAN_ERRALARM) != 0) + errMsgList.Add("CAN控制器错误报警"); + if ((errInfo.ErrCode & (uint)CAN_API.ErrorType.ERR_CAN_PASSIVE) != 0) + errMsgList.Add("CAN控制器消极错误"); + if ((errInfo.ErrCode & (uint)CAN_API.ErrorType.ERR_CAN_LOSE) != 0) + errMsgList.Add("CAN控制器仲裁丢失"); + if ((errInfo.ErrCode & (uint)CAN_API.ErrorType.ERR_CAN_BUSERR) != 0) + errMsgList.Add("CAN控制器总线错误"); + if ((errInfo.ErrCode & (uint)CAN_API.ErrorType.ERR_DEVICEOPENED) != 0) + errMsgList.Add("设备已经打开"); + if ((errInfo.ErrCode & (uint)CAN_API.ErrorType.ERR_DEVICEOPEN) != 0) + errMsgList.Add("打开设备错误"); + if ((errInfo.ErrCode & (uint)CAN_API.ErrorType.ERR_DEVICENOTOPEN) != 0) + errMsgList.Add("设备没有打开"); + if ((errInfo.ErrCode & (uint)CAN_API.ErrorType.ERR_BUFFEROVERFLOW) != 0) + errMsgList.Add("缓冲区溢出"); + if ((errInfo.ErrCode & (uint)CAN_API.ErrorType.ERR_DEVICENOTEXIST) != 0) + errMsgList.Add("此设备不存在"); + if ((errInfo.ErrCode & (uint)CAN_API.ErrorType.ERR_LOADKERNELDLL) != 0) + errMsgList.Add("装载动态库失败"); + if ((errInfo.ErrCode & (uint)CAN_API.ErrorType.ERR_CMDFAILED) != 0) + errMsgList.Add("执行命令失败"); + if ((errInfo.ErrCode & (uint)CAN_API.ErrorType.ERR_BUFFERCREATE) != 0) + errMsgList.Add("内存不足"); + + if (errMsgList.Count == 0) + { + // 若未检测到错误信息,则返回‘未知错误’ + return "未知错误"; + } + else + { + // 否则将错误信息以'|'拼接返回 + return string.Join("|", errMsgList); + } + } + + /// + /// 向CAN发送数据帧 + /// + /// 发送帧ID + /// 数据数组(数组长度必须为8) + public void SendData(uint frameID, byte[] data) + { + CAN_API.VCI_CAN_OBJ frameInfo = new CAN_API.VCI_CAN_OBJ + { + ID = frameID, // 帧ID + SendType = 0, // 正常发送 + RemoteFlag = 0, // 非远程帧 + ExternFlag = 0, // 非扩展帧 + DataLen = 8, // 数据长度 + Data = data, // 数据 + Reserved = new byte[3] // 预留 + }; + SendData(frameInfo); + } + + /// + /// 向CAN发送数据帧 + /// + /// 帧信息 + public void SendData(CAN_API.VCI_CAN_OBJ frameInfo) + { + // 发送一帧数据 + if (CAN_API.VCI_Transmit(_DeviceType, _DeviceInd, _CANInd, ref frameInfo, 1) == CAN_API.STATUS_ERR) + throw new Exception("数据发送失败,错误信息:" + ReadErrorMessage()); + } + + #endregion + + #region 生产者消费者模式 - 生产数据帧,发出事件消费数据帧 + /// + /// 帧缓冲区(生产者消费者队列) + /// + private BlockingCollection _FrameBuffer; + /// + /// 生产者线程 + /// + private Thread _ProducerThread; + /// + /// 消费者线程 + /// + private Thread _ConsumerThread; + /// + /// 消费帧事件委托 + /// + /// 报文帧 + public delegate void ConsumptionFrameEventHandler(CAN_API.VCI_CAN_OBJ frame); + /// + /// 消费帧事件 + /// + public event ConsumptionFrameEventHandler ConsumptionFrameEvent; + + /// + /// 开始工作线程 + /// + private void StartWork() + { + // 启动生产者与消费者线程 + // 优先级设置为高 + + _ProducerThread = new Thread(new ThreadStart(Producer)) + { + IsBackground = true, + Priority = ThreadPriority.Highest + }; + _ConsumerThread = new Thread(new ThreadStart(Consumer)) + { + IsBackground = true, + Priority = ThreadPriority.Highest + }; + _ProducerThread.Start(); + _ConsumerThread.Start(); + } + + /// + /// 生产者 + /// + private void Producer() + { + // 分配一个缓冲区,最大能同时容纳100帧数据 + IntPtr readBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CAN_API.VCI_CAN_OBJ)) * 100); ; + while (IsOpen) + { + // var n = CAN_API.VCI_GetReceiveNum(_DeviceType, _DeviceInd, _CANInd); + // 接收数据,超时时间100ms,最大接收100个 + uint len = CAN_API.VCI_Receive(_DeviceType, _DeviceInd, _CANInd, readBuffer, 100, 100); + + if (len == 0) + { + // 如果未读到数据,读取错误信息 + ReadErrorMessage(); + } + else + { + // 将读取到的每一帧数据构造帧对象VCI_CAN_OBJ,装入帧缓冲区中(生产产品到库存) + for (int i = 0; i < len; i++) + { + // 实例化帧对象,装入帧缓冲区 + _FrameBuffer.Add((CAN_API.VCI_CAN_OBJ)Marshal.PtrToStructure((IntPtr)((uint)readBuffer + i * Marshal.SizeOf(typeof(CAN_API.VCI_CAN_OBJ))), typeof(CAN_API.VCI_CAN_OBJ))); + } + } + } + } + + /// + /// 消费者 + /// + private void Consumer() + { + while (IsOpen) + { + // 从队列中获取帧(该方法会线程安全的阻塞,当有数据装入时立刻返回) + var frame = _FrameBuffer.Take(); + // 消费帧 + if (ConsumptionFrameEvent != null) + ConsumptionFrameEvent(frame); + } + } + #endregion + + #region 私有成员 + /// + /// 设备类型 + /// + private const uint _DeviceType = (uint)CAN_API.PCIDeviceType.VCI_USBCAN1; + /// + /// 设备ID + /// + private const uint _DeviceInd = 0; + /// + /// 第几路CAN + /// + private const uint _CANInd = 0; + #endregion + } +} diff --git a/CANHelper/USBCAN/CAN_API.cs b/CANHelper/USBCAN/CAN_API.cs new file mode 100644 index 0000000..a111c98 --- /dev/null +++ b/CANHelper/USBCAN/CAN_API.cs @@ -0,0 +1,262 @@ +using System; +using System.Runtime.InteropServices; + + +namespace USBCAN +{ + public sealed class CAN_API + { + #region 数据类型定义 + /// + /// 接口卡类型定义 + /// + public enum PCIDeviceType + { + VCI_PCI5121 = 1, + VCI_PCI9810 = 2, + VCI_USBCAN1 = 3, + VCI_USBCAN2 = 4, + VCI_PCI9820 = 5, + VCI_CAN232 = 6, + VCI_PCI5110 = 7, + VCI_CANLITE = 8, + VCI_ISA9620 = 9, + VCI_ISA5420 = 10, + VCI_PC104CAN = 11, + VCI_CANETE = 12, + VCI_DNP9810 = 13, + VCI_PCI9840 = 14, + VCI_PCI9820I = 16 + } + + //函数调用返回状态值 + /// + /// 正常状态 + /// + public static readonly int STATUS_OK = 1; + /// + /// 发生错误 + /// + public static readonly int STATUS_ERR = 0; + + /// + /// 错误类型 + /// + public enum ErrorType + { + // --------------- CAN错误码 ------------------- + /// + /// CAN错误码:CAN控制器内部FIFO溢出 + /// + ERR_CAN_OVERFLOW = 0x0001, + /// + /// CAN错误码:CAN控制器错误报警 + /// + ERR_CAN_ERRALARM = 0x0002, + /// + /// CAN错误码:CAN控制器消极错误 + /// + ERR_CAN_PASSIVE = 0x0004, + /// + /// CAN错误码:CAN控制器仲裁丢失 + /// + ERR_CAN_LOSE = 0x0008, + /// + /// CAN错误码:CAN控制器总线错误 + /// + ERR_CAN_BUSERR = 0x0010, + + // --------------- 通用错误码 ------------------- + /// + /// 通用错误码:设备已经打开 + /// + ERR_DEVICEOPENED = 0x0100, + /// + /// 通用错误码:打开设备错误 + /// + ERR_DEVICEOPEN = 0x0200, + /// + /// 通用错误码:设备没有打开 + /// + ERR_DEVICENOTOPEN = 0x0400, + /// + /// 通用错误码:缓冲区溢出 + /// + ERR_BUFFEROVERFLOW = 0x0800, + /// + /// 通用错误码:此设备不存在 + /// + ERR_DEVICENOTEXIST = 0x1000, + /// + /// 通用错误码:装载动态库失败 + /// + ERR_LOADKERNELDLL = 0x2000, + /// + /// 通用错误码:执行命令失败错误码 + /// + ERR_CMDFAILED = 0x4000, + /// + /// 通用错误码:内存不足 + /// + ERR_BUFFERCREATE = 0x8000 + } + + /// + /// ZLGCAN系列接口卡信息 + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct VCI_BOARD_INFO + { + public ushort hw_Version; + public ushort fw_Version; + public ushort dr_Version; + public ushort in_Version; + public ushort irq_Num; + public byte can_Num; + [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 20)] + public string str_Serial_Num; + [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 40)] + public string str_hw_Type; + [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = System.Runtime.InteropServices.UnmanagedType.U2)] + public ushort[] Reserved; + } + + /// + /// CAN信息帧 + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct VCI_CAN_OBJ + { + 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, ArraySubType = UnmanagedType.I1)] + public byte[] Data; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.I1)] + public byte[] Reserved; + } + + /// + /// CAN控制器状态 + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct VCI_CAN_STATUS + { + public byte ErrInterrupt; + public byte regMode; + public byte regStatus; + public byte regALCapture; + public byte regECCapture; + public byte regEWLimit; + public byte regRECounter; + public byte regTECounter; + public uint Reserved; + } + + /// + /// 错误信息 + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct VCI_ERR_INFO + { + public uint ErrCode; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.I1)] + public byte[] Passive_ErrData; + + public byte ArLost_ErrData; + } + + /// + /// 初始化CAN的配置信息 + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct VCI_INIT_CONFIG + { + /// + /// 验收码 + /// + public uint AccCode; + /// + /// 屏蔽码 + /// + public uint AccMask; + /// + /// 预留,填0 + /// + public uint Reserved; + /// + /// 滤波方式 + /// + public byte Filter; + public byte Timing0; + public byte Timing1; + /// + /// 模式:0:正常|1:只听 + /// + public byte Mode; + } + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct CHGDESIPANDPORT + { + [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 10)] + public string szpwd; + + [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 20)] + public string szdesip; + + public int desport; + } + #endregion + + #region API函数 + + [DllImport("ControlCAN.dll", SetLastError = true, EntryPoint = "VCI_OpenDevice", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern uint VCI_OpenDevice(uint DeviceType, uint DeviceInd, uint Reserved); + + [DllImport("ControlCAN.dll", SetLastError = true, EntryPoint = "VCI_CloseDevice", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern uint VCI_CloseDevice(uint DeviceType, uint DeviceInd); + + [DllImport("ControlCAN.dll", SetLastError = true, EntryPoint = "VCI_ResetCAN", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern uint VCI_ResetCAN(uint DeviceType, uint DeviceInd, uint CANInd); + + [DllImport("ControlCAN.dll", SetLastError = true, EntryPoint = "VCI_InitCAN", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern uint VCI_InitCAN(uint DeviceType, uint DeviceInd, uint CANInd, ref VCI_INIT_CONFIG pInitConfig); + + [DllImport("ControlCAN.dll", SetLastError = true, EntryPoint = "VCI_Transmit", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern uint VCI_Transmit(uint DeviceType, uint DeviceInd, uint CANInd, ref VCI_CAN_OBJ pSend, uint Len); + + [DllImport("ControlCAN.dll", SetLastError = true, EntryPoint = "VCI_Receive", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern uint VCI_Receive(uint DeviceType, uint DeviceInd, uint CANInd, IntPtr pReceive, uint Len, int WaitTime); + + [DllImport("ControlCAN.dll", SetLastError = true, EntryPoint = "VCI_GetReceiveNum", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern uint VCI_GetReceiveNum(uint DeviceType, uint DeviceInd, uint CANInd); + + [DllImport("ControlCAN.dll", SetLastError = true, EntryPoint = "VCI_ClearBuffer", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern uint VCI_ClearBuffer(uint DeviceType, uint DeviceInd, uint CANInd); + + [DllImport("ControlCAN.dll", SetLastError = true, EntryPoint = "VCI_ReadErrInfo", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern uint VCI_ReadErrInfo(uint DeviceType, uint DeviceInd, uint CANInd, ref VCI_ERR_INFO pErrInfo); + + [DllImport("ControlCAN.dll", SetLastError = true, EntryPoint = "VCI_StartCAN", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern uint VCI_StartCAN(uint DeviceType, uint DeviceInd, uint CANInd); + + [DllImport("ControlCAN.dll", SetLastError = true, EntryPoint = "VCI_SetReference", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern uint VCI_SetReference(uint DeviceType, uint DeviceInd, uint CANInd, uint RefType, object pData); + + [DllImport("ControlCAN.dll", SetLastError = true, EntryPoint = "VCI_GetReference", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern uint VCI_GetReference(uint DeviceType, uint DeviceInd, uint CANInd, uint RefType, object pData); + + [DllImport("ControlCAN.dll", SetLastError = true, EntryPoint = "VCI_ReadCANStatus", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern uint VCI_ReadCANStatus(uint DeviceType, uint DeviceInd, uint CANInd, ref VCI_CAN_STATUS pCANStatus); + + [DllImport("ControlCAN.dll", SetLastError = true, EntryPoint = "VCI_ReadBoardInfo", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] + public static extern uint VCI_ReadBoardInfo(uint DeviceType, uint DeviceInd, ref VCI_BOARD_INFO pInfo); + + #endregion + } +} diff --git a/CANHelper/kerneldlls/kerneldll.ini b/CANHelper/kerneldlls/kerneldll.ini new file mode 100644 index 0000000..ed6204d --- /dev/null +++ b/CANHelper/kerneldlls/kerneldll.ini @@ -0,0 +1,28 @@ +[KERNELDLL] +COUNT=26 +1=PCI5121.dll +2=PCI9810B.dll +3=USBCAN.dll +4=USBCAN.dll +5=PCI9820B.dll +6=CAN232.dll +7=PCI5121.dll +8=CANLite.dll +9=ISA9620B.dll +10=ISA5420.dll +11=PC104CAN.dll +12=CANETE.dll +13=DNP9810B.dll +14=PCI9840B.dll +15=PC104C2.dll +16=PCI9820I.dll +17=CANET_TCP.dll +18=pec9920.dll +19=pci50xx_u.dll +20=USBCAN_E.dll +21=USBCAN_E.dll +22=pci50xx_u.dll +23=topcliff_can.dll +24=pcie9221.dll +25=CANWIFI_TCP.dll +26=CANWIFI_UDP.dll diff --git a/CANHelper/kerneldlls/usbcan.dll b/CANHelper/kerneldlls/usbcan.dll new file mode 100644 index 0000000000000000000000000000000000000000..0d5e36aab9b9dc814e35a4e7b9fc9a616d8e14a1 GIT binary patch literal 26112 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 literal 0 HcmV?d00001