C#开发CANopen主站(SDO收发数据)

C#开发CANopen主站(SDO收发数据)

一、准备工作

  • 熟悉CANopen相关知识,可以参考我的另一篇博客《CANopen学习笔记》
  • 获取周立功或者广成科技的上位机二次开发包及驱动
  • 熟悉二次开发包(以周立功二次开发包为例)
  • 了解开发需求

二、修改二次开发包(以周立功USBCAN为例)

因为用不到CAN的高速功能(CAN_FD),因此相关的都可以删除或者注释掉。
修改后,最重要的三个函数和一个数据接收委托事件就是

  • CANDeviceStart():启动CAN
  • CANDeviceClose():关闭CAN
  • CANDataSend:发送CAN
  • USBCANReceiveData:CAN接收委托事件
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MCUCommunication.USBCAN.ZLGCAN
{
    public class UsbCANUtil
    {
        protected Logger UsbCANLogger { get; set; }
        //定义线程委托,用于接收数据处理
        public delegate void USBCANReceiveData(byte[] revdata);
        public delegate void RxMessage(ZCAN_Receive_Data msg, bool bridge = false);
        public delegate void DisconnectDelegate(CommunicationHelper.CommunicatinType type);

        #region CAN参数定义
        const int NULL = 0;
        //const int CANFD_BRS = 0x01; /* bit rate switch (second bitrate for payload data) */
        //const int CANFD_ESI = 0x02; /* error state indicator of the transmitting node */

        /* CAN payload length and DLC definitions according to ISO 11898-1 */
        //const int CAN_MAX_DLC = 8;
        const int CAN_MAX_DLEN = 8;

        /* CAN FD payload length and DLC definitions according to ISO 11898-7 */
        //const int CANFD_MAX_DLC = 15;
        //const int CANFD_MAX_DLEN = 64;

        const uint CAN_EFF_FLAG = 0x80000000U; /* EFF/SFF is set in the MSB */
        const uint CAN_RTR_FLAG = 0x40000000U; /* remote transmission request */
        const uint CAN_ERR_FLAG = 0x20000000U; /* error message frame */
        const uint CAN_ID_FLAG = 0x1FFFFFFFU; /* id */

        public DeviceInfo[] kDeviceType =
        {
            new DeviceInfo(Define.ZCAN_USBCAN1, 1),
            new DeviceInfo(Define.ZCAN_USBCAN2, 2),
            new DeviceInfo(Define.ZCAN_USBCAN_E_U, 1),
            new DeviceInfo(Define.ZCAN_USBCAN_2E_U, 2),
            //目前本上位机只支持周立功USBCAN的四种设备,后期根据需求再添加
            //new DeviceInfo(Define.ZCAN_PCIECANFD_100U, 1),
            //new DeviceInfo(Define.ZCAN_PCIECANFD_200U, 2),
            //new DeviceInfo(Define.ZCAN_PCIECANFD_400U, 4),
            //new DeviceInfo(Define.ZCAN_USBCANFD_200U, 2),
            //new DeviceInfo(Define.ZCAN_USBCANFD_100U, 1),
            //new DeviceInfo(Define.ZCAN_USBCANFD_MINI, 1),
            //new DeviceInfo(Define.ZCAN_CANETTCP, 1),
            //new DeviceInfo(Define.ZCAN_CANETUDP, 1),
            //new DeviceInfo(Define.ZCAN_CLOUD, 1)
        };

        uint[] kAbitTiming =
        {
            0x00018B2E,//1Mbps
	        0x00018E3A,//800kbps
	        0x0001975E,//500kbps
	        0x0001AFBE,//250kbps
	        0x0041AFBE,//125kbps
	        0x0041BBEE,//100kbps
	        0x00C1BBEE //50kbps
        };

        uint[] kDbitTiming =
        {
            0x00010207,//5Mbps
	        0x0001020A,//4Mbps
	        0x0041020A,//2Mbps
	        0x0081830E //1Mbps
        };

        byte[] kTiming0 =
        {
            0x00, //1000kbps
            0x00, //800kbps
            0x00, //500kbps
            0x01, //250kbps
            0x03, //125kbps
            0x04, //100kbps
            0x09, //50kbps
            0x18, //20kbps
            0x31, //10kbps
            0xBF  //5kbps
        };
        byte[] kTiming1 =
        {
            0x14,//1000kbps
            0x16,//800kbps
            0x1C,//500kbps
            0x1C,//250kbps
            0x1C,//125kbps
            0x1C,//100kbps
            0x1C,//50kbps
            0x1C,//20kbps
            0x1C,//10kbps
            0xFF //5kbps
        };
        uint[] kBaudrate =
        {
            1000000,//1000kbps
            800000,//800kbps
            500000,//500kbps
            250000,//250kbps
            125000,//125kbps
            100000,//100kbps
            50000,//50kbps
            20000,//20kbps
            10000,//10kbps
            5000 //5kbps
        };
        #endregion

        int channel_index_;
        IntPtr device_handle_;
        IntPtr channel_handle_;
        IProperty property_;
        RecvDataThread recv_data_thread_;

        private int DeviceTypeIndex;//设备类型索引,对应ComboBox下拉框
        private int DeviceIndex=0;//设备索引,对应ComboBox下拉框,本上位机只针对一个USBCAN设备调试,不存在接入多个相同设备的情况,因此设备索引一直为0
        private int BuadRateIndex;//波特率索引,对应ComboBox下拉框

        public event USBCANReceiveData USBCANReceiveEvent;
        public event RxMessage rxmessage;
        public event DisconnectDelegate disconnectEvent;

        private static readonly Lazy<UsbCANUtil> lazy = new Lazy<UsbCANUtil>(() => new UsbCANUtil());
        public static UsbCANUtil Instance => lazy.Value;

        public ManualResetEvent manualResetEvent = new ManualResetEvent(false);

        #region 构造函数
        public UsbCANUtil()
        {
            
        }
        #endregion

        #region 生成CAN ID
        public uint MakeCanId(uint id, int eff, int rtr, int err)//1:extend frame 0:standard frame
        {
            uint ueff = (uint)(!!(Convert.ToBoolean(eff)) ? 1 : 0);
            uint urtr = (uint)(!!(Convert.ToBoolean(rtr)) ? 1 : 0);
            uint uerr = (uint)(!!(Convert.ToBoolean(err)) ? 1 : 0);
            return id | ueff << 31 | urtr << 30 | uerr << 29;
        }
        #endregion

        #region 判断拓展帧还是标准帧
        public bool IsEFF(uint id)//1:extend frame 0:standard frame
        {
            return !!Convert.ToBoolean((id & CAN_EFF_FLAG));
        }
        #endregion

        #region 判断是远程帧还是数据帧
        public bool IsRTR(uint id)//1:remote frame 0:data frame
        {
            return !!Convert.ToBoolean((id & CAN_RTR_FLAG));
        }
        #endregion

        #region 判断是错误帧还是正常帧
        public bool IsERR(uint id)//1:error frame 0:normal frame
        {
            return !!Convert.ToBoolean((id & CAN_ERR_FLAG));
        }
        #endregion

        #region 获取ID
        public uint GetId(uint id)
        {
            return id & CAN_ID_FLAG;
        }
        #endregion

        #region CAN设备关闭
        public void CANDeviceClose()
        {
            //if (CANDeviceOpen())
            //{
            //    Method.ZCAN_CloseDevice(device_handle_);
            //}
            Method.ZCAN_CloseDevice(device_handle_);
        }
        #endregion

        #region CAN设备开启
        /// 
        /// CAN设备开启
        /// 
        /// 设备是否开启
        public bool CANDeviceOpen()
        {
            //开启USBCAN端口
            uint device_type_index_ = (uint)DeviceTypeIndex;
            uint device_index_;
            device_index_ = (uint)DeviceIndex;
            try
            {
                device_handle_ = Method.ZCAN_OpenDevice(kDeviceType[device_type_index_].device_type, device_index_, 0);
            }
            catch (Exception)
            {
                MessageBox.Show("DLL运行不匹配,请选择其他版本APP!");
                return false;
            }

            if (NULL == (int)device_handle_)
            {
                //MessageBox.Show("打开设备失败,请检查设备类型和设备索引号是否正确", "提示",
                //        MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                //UsbCANLogger.Info("打开设备失败,请检查设备类型和设备索引号是否正确");
                return false;
            }
            return true;
        }
        #endregion

        #region 初始化CAN设备
        private bool InitCanDevice()
        {
            if (!CANDeviceOpen())
            {
                //MessageBox.Show("设备还没打开", "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                //UsbCANLogger.Info("设备还没打开");
                return false;
            }

            try
            {
                //uint type = kDeviceType[DeviceIndex].device_type;
                IntPtr ptr = Method.GetIProperty(device_handle_);
                if (NULL == (int)ptr)
                {
                    MessageBox.Show("设置指定路径属性失败", "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    //UsbCANLogger.Info("设置指定路径属性失败");
                    return false;
                }

                property_ = (IProperty)Marshal.PtrToStructure((IntPtr)((UInt32)ptr), typeof(IProperty));

                if (!setBaudrate(kBaudrate[BuadRateIndex]))
                {
                    MessageBox.Show("设置波特率失败", "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    //UsbCANLogger.Info("设置波特率失败");
                    return false;
                }

                ZCAN_CHANNEL_INIT_CONFIG config_ = new ZCAN_CHANNEL_INIT_CONFIG();
                config_.canfd.mode = (byte)0;//0 正常模式 ,1 只听模式
                config_.can_type = Define.TYPE_CAN;
                config_.can.timing0 = kTiming0[BuadRateIndex];
                config_.can.timing1 = kTiming1[BuadRateIndex];
                config_.can.filter = 0;
                config_.can.acc_code = 0;
                config_.can.acc_mask = 0xFFFFFFFF;

                IntPtr pConfig = Marshal.AllocHGlobal(Marshal.SizeOf(config_));
                Marshal.StructureToPtr(config_, pConfig, true);

                //int size = sizeof(ZCAN_CHANNEL_INIT_CONFIG);
                //IntPtr ptr = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(size);
                //System.Runtime.InteropServices.Marshal.StructureToPtr(config_, ptr, true);
                channel_handle_ = Method.ZCAN_InitCAN(device_handle_, (uint)channel_index_, pConfig);
                Marshal.FreeHGlobal(pConfig);

                if (NULL == (int)channel_handle_)
                {
                    //MessageBox.Show("初始化CAN失败", "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    //UsbCANLogger.Info("初始化CAN失败");
                    return false;
                }

            }
            catch (Exception ex)
            {
                UsbCANLogger.Info(ex.Message);
                return false;
            }
            return true;

        }
        #endregion

        #region 启动CAN
        public bool CANDeviceStart()
        {
            if (!InitCanDevice())
            {
                //MessageBox.Show("初始化CAN失败", "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                //UsbCANLogger.Info("初始化CAN失败");
                return false;
            }

            if (Method.ZCAN_StartCAN(channel_handle_) != Define.STATUS_OK)
            {
                //MessageBox.Show("启动CAN失败", "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                //UsbCANLogger.Info("启动CAN失败");
                return false;
            }

            if (null == recv_data_thread_)
            {
                recv_data_thread_ = new RecvDataThread();
                recv_data_thread_.setChannelHandle(channel_handle_);
                recv_data_thread_.setStart(true);
                recv_data_thread_.RecvCANData += this.AddData;
                //recv_data_thread_.RecvFDData += this.AddData;
            }
            else
            {
                recv_data_thread_.setChannelHandle(channel_handle_);
            }
            return true;
        }
        #endregion

        #region 复位CAN
        public bool CANDeviceReset()
        {
            if (Method.ZCAN_ResetCAN(channel_handle_) != Define.STATUS_OK)
            {
                //MessageBox.Show("复位失败", "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                //UsbCANLogger.Info("复位失败");
                return false;
            }
            return true;
        }
        #endregion

        #region 数据发送
        public bool CANDataSend(string IDstr,string DataText)
        {
            if (DataText.Length == 0)
            {
                //MessageBox.Show("数据为空", "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                //UsbCANLogger.Info("数据为空");
                return false;
            }

            uint id = (uint)System.Convert.ToInt32(IDstr, 16);
            string data = DataText;
            uint result; //发送的帧数

            ZCAN_Transmit_Data can_data = new ZCAN_Transmit_Data();
            can_data.frame.can_id = MakeCanId(id, 0, 0, 0);  
            can_data.frame.data = new byte[8];
            can_data.frame.can_dlc = (byte)SplitData(data, ref can_data.frame.data, CAN_MAX_DLEN);
            can_data.transmit_type = (uint)0;  //0正常发送 1单次发送 2自发自收 3单次自发自收
            IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(can_data));
            Marshal.StructureToPtr(can_data, ptr, true);

            result = Method.ZCAN_Transmit(channel_handle_, ptr, 1);
            Marshal.FreeHGlobal(ptr);

            if (result != 1)
            {
                //MessageBox.Show("发送数据失败", "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                //UsbCANLogger.Info("发送数据失败");
                AddErr();
                return false;
            }

            return true;
        }

        public bool CANDataSend(uint id, byte[] Data)
        {
            if (Data.Length == 0 && Data.Length > 8)
            {
                //MessageBox.Show("数据为空,或者超出范围", "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                //UsbCANLogger.Info("数据为空,或者超出范围");
                return false;
            }

            uint result=0; //发送的帧数

            ZCAN_Transmit_Data can_data = new ZCAN_Transmit_Data();
            can_data.frame.can_id = MakeCanId(id, 0, 0, 0);
            can_data.frame.data = Data;
            can_data.frame.can_dlc = (byte)Data.Length;
            can_data.transmit_type = (uint)0;  //0正常发送 1单次发送 2自发自收 3单次自发自收
            IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(can_data));
            Marshal.StructureToPtr(can_data, ptr, true);

            lock (recv_data_thread_)
            {
                try
                {
                    int times = 0;
                    var flag = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(3));
                    if (flag)
                    {
                        times = 0;
                        //IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(can_data));
                        //Marshal.StructureToPtr(can_data, ptr, true);
                        result = Method.ZCAN_Transmit(channel_handle_, ptr, 1);
                        manualResetEvent.Reset();
                        Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fffff :::  1  :::") + string.Format("{0:X2} {1:X2}", Data[1], Data[2]) + "///" + result);
                        Marshal.FreeHGlobal(ptr);

                        if (result != 1)
                        {
                            AddErr();
                            return false;
                        }
                        else
                        {
                            SaveDataToLog(can_data);
                        }
                    }
                    else
                    {
                        while (!flag)
                        {
                            //times++;
                            //IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(can_data));
                            //Marshal.StructureToPtr(can_data, ptr, true);
                            result = Method.ZCAN_Transmit(channel_handle_, ptr, 1);
                            manualResetEvent.Reset();
                            Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fffff ::: 2 :::") + string.Format("{0:X2} {1:X2}", Data[1], Data[2]) + "///" + result);
                            //Marshal.FreeHGlobal(ptr);

                            if (result != 1)
                            {
                                times++;
                                AddErr();
                                if (times>0)
                                {
                                    Marshal.FreeHGlobal(ptr);
                                    times = 0;
                                    CommunicationHelper.Instance.DeviceDisConnect(CommunicationHelper.CommunicatinType.USBCAN);
                                    if (disconnectEvent != null)
                                    {
                                        disconnectEvent(CommunicationHelper.CommunicatinType.USBCAN);
                                    }
                                    return false;
                                }
                            }
                            else
                            {
                                SaveDataToLog(can_data);
                                Marshal.FreeHGlobal(ptr);
                                break;
                            }

                        }
                    }
                }
                catch (Exception)
                {

                }
        }
            return true;
        }
        #endregion

        #region 更改CAN设备的通道数
        public void SetCANDeviceChannel(int kDeviceTypeindex,int Channel)
        {
            try
            {
                int Num = (int)kDeviceType[kDeviceTypeindex].channel_count;
                if (Channel >= Num)
                {
                    return;
                }
                channel_index_ = Channel;
            }
            catch (Exception ex)
            {
                MessageBox.Show("出现错误!");
            }

        }
        #endregion

        #region SetDeviceTypeIndex
        public void SetDeviceTypeIndex(int deviceTypeIndex)
        {
            DeviceTypeIndex = deviceTypeIndex;
        }
        #endregion

        #region SetBuadRateIndex
        public void SetBuadRateIndex(int buadRateIndex)
        {
            BuadRateIndex = buadRateIndex;
        }
        #endregion

        #region 获取设备的通道数并保存到列表中,ComboBox源
        public List<int> GetCANDeviceChannelNum(int kDeviceTypeIndex)
        {
            int Num = (int)kDeviceType[kDeviceTypeIndex].channel_count;
            var list = new List<int>();
            for (int i = 0; i < Num; i++)
            {
                list.Add(i);
            }
            return list;
        }
        #endregion

        //设置波特率
        private bool setBaudrate(UInt32 baud)
        {
            string path = channel_index_ + "/baud_rate";
            string value = baud.ToString();
            byte[] byteArray = System.Text.Encoding.Default.GetBytes(value);
            //char* pathCh = (char*)System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(path).ToPointer();
            //char* valueCh = (char*)System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi(value).ToPointer();
            return 1 == property_.SetValue(path, byteArray);
        }

        private void AddData(ZCAN_Receive_Data[] data, uint len)
        {
            //string list_box_data_ = "";
            for (uint i = 0; i < len; ++i)
            {
                byte[] revData=new byte[11];//ID + DLC +Data,最长11个字节
                ZCAN_Receive_Data can = data[i];
                uint id = data[i].frame.can_id;

                revData[0]= (byte)(id / 256);
                revData[1]= (byte)(id % 256);
                revData[2]= (byte)can.frame.can_dlc;

                byte[] tarData = new byte[3+ can.frame.can_dlc];

                string str = string.Format("{0:X2} {1:X2} {2:X2} ", revData[0], revData[1], revData[2]);

                for (uint j = 0; j < can.frame.can_dlc; ++j)
                {
                    revData[3 + j] = (byte)can.frame.data[j];
                    str += string.Format("{0:X2} ", revData[3 + j]);
                }

                Array.Copy(revData,tarData,tarData.Length);//拷贝数组,不是每个Data都是8个字节

                Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fffff :::")+str);

                CommunicationHelper.Instance.DebugLogAdd(str);
                manualResetEvent.Set();         //Set() 方法的调用使得ManualResetEvent对象的bool变量值为True,所有线程被释放并继续执行
                manualResetEvent.Reset();       //如果我们想多次发送信号,那么我们必须在调用Set()方法后立即调用Reset()方法。

                if (USBCANReceiveEvent != null)
                {
                    USBCANReceiveEvent(tarData);
                }

                if (rxmessage != null)
                {
                    rxmessage(can);
                }

                
            }


        }

        private string AddErr()
        {
            ZCAN_CHANNEL_ERROR_INFO pErrInfo = new ZCAN_CHANNEL_ERROR_INFO();
            IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(pErrInfo));
            Marshal.StructureToPtr(pErrInfo, ptr, true);
            if (Method.ZCAN_ReadChannelErrInfo(channel_handle_, ptr) != Define.STATUS_OK)
            {
                //UsbCANLogger.Info("获取错误信息失败");
                MessageBox.Show("获取错误信息失败", "提示", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
            Marshal.FreeHGlobal(ptr);

            string errorInfo = String.Format("错误码:{0:D1}", pErrInfo.error_code);
            //UsbCANLogger.Info(errorInfo);
            //MessageBox.Show(errorInfo);
            return errorInfo;
        }

        //拆分text到发送data数组
        private int SplitData(string data, ref byte[] transData, int maxLen)
        {
            string[] dataArray = data.Split(' ');
            for (int i = 0; (i < maxLen) && (i < dataArray.Length); i++)
            {
                transData[i] = Convert.ToByte(dataArray[i].Substring(0, 2), 16);
            }

            return dataArray.Length;
        }

        private void SaveDataToLog(ZCAN_Transmit_Data can_data)
        {
            byte[] SendData = new byte[11];
            SendData[0] = (byte)(can_data.frame.can_id / 256);
            SendData[1] = (byte)(can_data.frame.can_id % 256);
            SendData[2] = (byte)can_data.frame.can_dlc;
            Array.Copy(can_data.frame.data, 0, SendData, 3, can_data.frame.can_dlc);
            var str = CommunicationHelper.Instance.ByteToStr(SendData);
            CommunicationHelper.Instance.DebugLogAdd(str);
        }

    }
}

三、修改接收线程文件

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;
using System.IO;

namespace MCUCommunication.USBCAN.ZLGCAN
{
    //接收数据线程类
    public class RecvDataThread
    {
        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        public delegate void RecvCANDataEventHandler(ZCAN_Receive_Data[] data, uint len);//CAN数据接收事件委托

        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        public delegate void RecvFDDataEventHandler(ZCAN_ReceiveFD_Data[] data, uint len);//CANFD数据接收事件委托

        const int TYPE_CAN = 0;
        const int TYPE_CANFD = 1;

        bool m_bStart;
        IntPtr channel_handle_;
        Thread recv_thread_;
        static object locker = new object();
        public static RecvCANDataEventHandler OnRecvCANDataEvent;
        //public static RecvFDDataEventHandler OnRecvFDDataEvent;
        public RecvDataThread()
        {
        }

        public event RecvCANDataEventHandler RecvCANData
        {
            add { OnRecvCANDataEvent += new RecvCANDataEventHandler(value); }
            remove { OnRecvCANDataEvent -= new RecvCANDataEventHandler(value); }
        }

        //public event RecvFDDataEventHandler RecvFDData
        //{
        //    add { OnRecvFDDataEvent += new RecvFDDataEventHandler(value); }
        //    remove { OnRecvFDDataEvent -= new RecvFDDataEventHandler(value); }
        //}

        public void setStart(bool start)
        {
            m_bStart = start;
            if (start)
            {
                recv_thread_ = new Thread(RecvDataFunc);
                recv_thread_.IsBackground = true;
                recv_thread_.Start();
            }
            else
            {
                recv_thread_.Join();
                recv_thread_ = null;
            }
        }

        public void setChannelHandle(IntPtr channel_handle)
        {
            lock(locker)
            {
                channel_handle_ = channel_handle;
            }
        }

        //数据接收函数
        protected void RecvDataFunc()
        {
            ZCAN_Receive_Data[] can_data = new ZCAN_Receive_Data[10000];
            //ZCAN_ReceiveFD_Data[] canfd_data = new ZCAN_ReceiveFD_Data[10000];
            uint len;
            try
            {
                while (m_bStart)
                {
                    lock (locker)
                    {
                        len = Method.ZCAN_GetReceiveNum(channel_handle_, TYPE_CAN);
                        if (len > 0)
                        {
                            int size = Marshal.SizeOf(typeof(ZCAN_Receive_Data));
                            IntPtr ptr = Marshal.AllocHGlobal((int)len * size);
                            len = Method.ZCAN_Receive(channel_handle_, ptr, len, 50);
                            for (int i = 0; i < len; ++i)
                            {
                                can_data[i] = (ZCAN_Receive_Data)Marshal.PtrToStructure(
                                    (IntPtr)((Int64)ptr + i * size), typeof(ZCAN_Receive_Data));
                            }
                            //SaveDataToLog(can_data[0]);
                            OnRecvCANDataEvent(can_data, len);
                            Marshal.FreeHGlobal(ptr);
                        }

                        //len = Method.ZCAN_GetReceiveNum(channel_handle_, TYPE_CANFD);
                        //if (len > 0)
                        //{
                        //    int size = Marshal.SizeOf(typeof(ZCAN_ReceiveFD_Data));
                        //    IntPtr ptr = Marshal.AllocHGlobal((int)len * size);
                        //    len = Method.ZCAN_ReceiveFD(channel_handle_, ptr, len, 50);
                        //    for (int i = 0; i < len; ++i)
                        //    {
                        //        canfd_data[i] = (ZCAN_ReceiveFD_Data)Marshal.PtrToStructure(
                        //            (IntPtr)((UInt32)ptr+i*size), typeof(ZCAN_ReceiveFD_Data));
                        //    }
                        //    OnRecvFDDataEvent(canfd_data, len);
                        //    Marshal.FreeHGlobal(ptr);
                        //}
                    }

                    Thread.Sleep(1);
                }
            }
            catch (Exception ex)
            {

            }

        }

        private void SaveDataToLog(ZCAN_Receive_Data can_data)
        {
            byte[] SendData = new byte[11];
            SendData[0] = (byte)(can_data.frame.can_id / 256);
            SendData[1] = (byte)(can_data.frame.can_id % 256);
            SendData[2] = (byte)can_data.frame.can_dlc;
            Array.Copy(can_data.frame.data, 0, SendData, 3, can_data.frame.can_dlc);
            var str = CommunicationHelper.Instance.ByteToStr(SendData);
            CommunicationHelper.Instance.DebugLogAdd(str);
        }
    }
}

四、添加读取DLL的封装文件(二次开发包复制过来即可)

using System;
using System.Runtime.InteropServices;

namespace MCUCommunication.USBCAN.ZLGCAN
{
    [StructLayout(LayoutKind.Sequential)]
    public struct ZCAN
    {
        public uint acc_code;
        public uint acc_mask;
        public uint reserved;
        public byte filter;
        public byte timing0;
        public byte timing1;
        public byte mode;
    };

    [StructLayout(LayoutKind.Sequential)]
    public struct CANFD
    {
        public uint acc_code;
        public uint acc_mask;
        public uint abit_timing;
        public uint dbit_timing;
        public uint brp;
        public byte filter;
        public byte mode;
        public UInt16 pad;
        public uint reserved;
    };

    [StructLayout(LayoutKind.Sequential)]
    public struct can_frame
    {
        public uint can_id;  /* 32 bit MAKE_CAN_ID + EFF/RTR/ERR flags */
        public byte can_dlc; /* frame payload length in byte (0 .. CAN_MAX_DLEN) */
        public byte __pad;   /* padding */
        public byte __res0;  /* reserved / padding */
        public byte __res1;  /* reserved / padding */
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[] data/* __attribute__((aligned(8)))*/;
    };

    [StructLayout(LayoutKind.Sequential)]
    public struct canfd_frame
    {
        public uint can_id;  /* 32 bit MAKE_CAN_ID + EFF/RTR/ERR flags */
        public byte len;     /* frame payload length in byte */
        public byte flags;   /* additional flags for CAN FD,i.e error code */
        public byte __res0;  /* reserved / padding */
        public byte __res1;  /* reserved / padding */
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
        public byte[] data/* __attribute__((aligned(8)))*/;
    };

    [StructLayout(LayoutKind.Explicit)]
    public struct ZCAN_CHANNEL_INIT_CONFIG
    {
        [FieldOffset(0)]
        public uint can_type; //type:TYPE_CAN TYPE_CANFD

        [FieldOffset(4)]
        public ZCAN can;

        [FieldOffset(4)]
        public CANFD canfd;
    };

    [StructLayout(LayoutKind.Sequential)]
    public struct ZCAN_Transmit_Data
    {
        public can_frame frame;
        public uint transmit_type;
    };

    [StructLayout(LayoutKind.Sequential)]
    public struct ZCAN_Receive_Data
    {
        public can_frame frame;
        public UInt64 timestamp;//us
    };

    [StructLayout(LayoutKind.Sequential)]
    public struct ZCAN_TransmitFD_Data
    {
        public canfd_frame frame;
        public uint transmit_type;
    };

    public struct DeviceInfo
    {
        public uint device_type;  //设备类型
        public uint channel_count;//设备的通道个数
        public DeviceInfo(uint type, uint count)
        {
            device_type = type;
            channel_count = count;
        }
    };

    [StructLayout(LayoutKind.Sequential)]
    public struct ZCAN_ReceiveFD_Data
    {
        public canfd_frame frame;
        public UInt64 timestamp;//us
    };

    [StructLayout(LayoutKind.Sequential)]
    public struct ZCAN_CHANNEL_ERROR_INFO
    {
        public uint error_code;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public byte[] passive_ErrData;
        public byte arLost_ErrData;
    } ;

    //for zlg cloud
    [StructLayout(LayoutKind.Sequential)]
    public struct ZCLOUD_DEVINFO
    {
        public int devIndex;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
        public char[] type;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
        public char[] id;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
        public char[] owner;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
        public char[] model;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        public char[] fwVer;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        public char[] hwVer;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
        public char[] serial;
        public byte canNum;
        public int status;             // 0:online, 1:offline
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        public byte[] bCanUploads;   // each channel enable can upload
        public byte bGpsUpload;
    };

    //[StructLayout(LayoutKind.Sequential)]
    //public struct ZCLOUD_DEV_GROUP_INFO
    //{
    //    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
    //    public char[] groupName;
    //    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
    //    public char[] desc;
    //    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
    //    public char[] groupId;
    //    //public ZCLOUD_DEVINFO *pDevices;
    //    public IntPtr pDevices;
    //    public uint devSize;
    //};

    [StructLayout(LayoutKind.Sequential)]
    public struct ZCLOUD_USER_DATA
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
        public char[] username;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
        public char[] mobile;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
        public char[] dllVer;
        //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
        // public char[] email;
        // public IntPtr pDevGroups;
        // public uint devGroupSize;
        public uint devCnt;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
        public ZCLOUD_DEVINFO[] devices;
    };

    public class Define
    {
        public const int TYPE_CAN = 0;
        public const int TYPE_CANFD = 1;

        public const int ZCAN_USBCAN1 = 3;
        public const int ZCAN_USBCAN2 = 4;
        public const int ZCAN_CANETUDP = 12;
        public const int ZCAN_CANETTCP = 17;
        public const int ZCAN_USBCAN_E_U = 20;
        public const int ZCAN_USBCAN_2E_U = 21;
        public const int ZCAN_PCIECANFD_100U = 38;
        public const int ZCAN_PCIECANFD_200U = 39;
        public const int ZCAN_PCIECANFD_400U = 40;
        public const int ZCAN_USBCANFD_200U = 41;
        public const int ZCAN_USBCANFD_100U = 42;
        public const int ZCAN_USBCANFD_MINI = 43;
        public const int ZCAN_CLOUD = 46;
        public const int ZCAN_CANFDNET_400U_TCP = 52;
        public const int ZCAN_CANFDNET_400U_UDP = 53;

        public const int STATUS_ERR = 0;
        public const int STATUS_OK = 1;
    };

    public class Method
    {
        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr ZCAN_OpenDevice(uint device_type, uint device_index, uint reserved);

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern uint ZCAN_CloseDevice(IntPtr device_handle);

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        // pInitConfig -> ZCAN_CHANNEL_INIT_CONFIG
        public static extern IntPtr ZCAN_InitCAN(IntPtr device_handle, uint can_index, IntPtr pInitConfig);

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern uint ZCAN_StartCAN(IntPtr channel_handle);

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern uint ZCAN_ResetCAN(IntPtr channel_handle);

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        // pTransmit -> ZCAN_Transmit_Data
        public static extern uint ZCAN_Transmit(IntPtr channel_handle, IntPtr pTransmit, uint len);

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        // pTransmit -> ZCAN_TransmitFD_Data
        public static extern uint ZCAN_TransmitFD(IntPtr channel_handle, IntPtr pTransmit, uint len);

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern uint ZCAN_GetReceiveNum(IntPtr channel_handle, byte type);

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern uint ZCAN_Receive(IntPtr channel_handle, IntPtr data, uint len, int wait_time = -1);

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern uint ZCAN_ReceiveFD(IntPtr channel_handle, IntPtr data, uint len, int wait_time = -1);

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        // pErrInfo -> ZCAN_CHANNEL_ERROR_INFO
        public static extern uint ZCAN_ReadChannelErrInfo(IntPtr channel_handle, IntPtr pErrInfo);

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr GetIProperty(IntPtr device_handle);

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern bool ZCLOUD_IsConnected();

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern void ZCLOUD_SetServerInfo(string httpAddr, ushort httpPort,
            string mqttAddr, ushort mqttPort);

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern uint ZCLOUD_ConnectServer(string username, string password);

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern uint ZCLOUD_DisconnectServer();

        [DllImport("zlgcan.dll", CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr ZCLOUD_GetUserData();
    }

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate int SetValueFunc(string path, byte[] value);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate IntPtr GetValueFunc(string path);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate IntPtr GetPropertysFunc(string path, string value);

    public struct IProperty
    {
        public SetValueFunc SetValue;
        public GetValueFunc GetValue;
        public GetPropertysFunc GetPropertys;
    };
}

需要注意的是,生成的exe同等级文件夹路径下需要添加dll库文件
C#开发CANopen主站(SDO收发数据)_第1张图片
在这里插入图片描述
还需要注意dll库兼容的是64位或32位系统(若出现无法读取dll文件的情况,大概率就是dll与系统不匹配的问题)
C#开发CANopen主站(SDO收发数据)_第2张图片

五、添加CANopen相关文件和修改

using DCMotorControlSystem.Commoms.Protocols;
using MCUCommunication.USBCAN.ECAN;
using MCUCommunication.USBCAN.ZLGCAN;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static MCUCommunication.CommunicationHelper;

namespace MCUCommunication.Protocols
{



    public class CanOpenSDOLib
    {
        private static readonly Lazy<CanOpenSDOLib> lazy = new Lazy<CanOpenSDOLib>(() => new CanOpenSDOLib());
        public static CanOpenSDOLib Instance => lazy.Value;

        private Queue<SDO> sdo_queue = new Queue<SDO>();
        public Dictionary<UInt16, SDO> SDOcallbacks = new Dictionary<ushort, SDO>();
        ConcurrentQueue<CanPacket> packetqueue = new ConcurrentQueue<CanPacket>();

        public delegate void PacketEvent(CanPacket p, DateTime dt);
        public event PacketEvent packetevent;

        public delegate void SDOEvent(CanPacket p, DateTime dt);
        public event SDOEvent sdoevent;
        
        public delegate void SDODelegate(CanPacket p);
        public event SDODelegate SDODelegateevent;

        public byte[] DownLoadData = null;

        bool threadrun = true;

        private readonly object lockThread = new object();

        public CanOpenSDOLib()
        {

        }

        /// 
        /// Send a Can packet on the bus
        /// 
        /// 
        public bool SendPacket(CommunicatinType type,CanPacket p, bool bridge = false)
        {

            var flag = false;
            switch (type)
            {
                case CommunicatinType.USBCAN:
                    ZCAN_Receive_Data msg = p.ToMsg();
                    flag = UsbCANUtil.Instance.CANDataSend(msg.frame.can_id, msg.frame.data);
                    if (flag)
                    {
                        Driver_rxmessage(msg, bridge);
                    }
                    break;
                case CommunicatinType.ECAN:
                    CAN_OBJ obj = p.ToMsg2();
                    flag = ECANUtil.Instance.DataSend(obj.ID,obj.data);
                    if (flag)
                    {
                        Driver_rxmessage(obj, bridge);
                    }
                    break;
                default:
                    break;
            }
            
            return flag;
        }


        /// 
        /// Recieved message callback handler
        /// 
        /// CanOpen message recieved from the bus
        private void Driver_rxmessage(ZCAN_Receive_Data msg, bool bridge = false)
        {
            //Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fffff ::: 2 :::") + string.Format("Driver_rxmessage {0:X2} {1:X2}", msg.frame.data[1], msg.frame.data[2]));
            //packetqueue.Enqueue(new CanPacket(msg, bridge));
            var p = new CanPacket(msg, bridge);
            packetqueue.Enqueue(new CanPacket(msg, bridge));
            if (SDODelegateevent != null)
            {
                SDODelegateevent(p);
            }
        }

        /// 
        /// Recieved message callback handler
        /// 
        /// CanOpen message recieved from the bus
        private void Driver_rxmessage(CAN_OBJ msg, bool bridge = false)
        {
            var p = new CanPacket(msg, bridge);
            packetqueue.Enqueue(new CanPacket(msg, bridge));
            if (SDODelegateevent != null)
            {
                SDODelegateevent(p);
            }
        }

        public void open(CommunicatinType type)
        {
            switch (type)
            {
                case CommunicatinType.USBCAN:
                    UsbCANUtil.Instance.rxmessage += Driver_rxmessage;
                    break;
                case CommunicatinType.SerialPort:
                    break;
                case CommunicatinType.ECAN:
                    ECANUtil.Instance.rxmessage += Driver_rxmessage;
                    break;
                default:
                    break;
            }


            threadrun = true;
            Thread thread = new Thread(new ThreadStart(asyncprocess));
            //Thread thread = new Thread(new ThreadStart(syncprocess)); 
            thread.Start();
        }

        

        /// 
        /// Close the CanOpen CanFestival driver
        /// 
        public void close(CommunicatinType type)
        {
            threadrun = false;

            switch (type)
            {
                case CommunicatinType.USBCAN:
                    UsbCANUtil.Instance.rxmessage -= Driver_rxmessage;
                    break;
                case CommunicatinType.SerialPort:
                    break;
                case CommunicatinType.ECAN:
                    ECANUtil.Instance.rxmessage -= Driver_rxmessage;
                    break;
                default:
                    break;
            }
        }

        void asyncprocess()
        {
            while (threadrun)
            {
                CanPacket cp;

                //while (threadrun && packetqueue.IsEmpty && sdo_queue.Count == 0 && SDO.isEmpty())
                while (packetqueue.IsEmpty && sdo_queue.Count == 0 && SDO.isEmpty())
                {
                    System.Threading.Thread.Sleep(1);
                }

                while (packetqueue.TryDequeue(out cp))
                {
                    //SDO replies 0x601-0x67F
                    if (cp.cob >= 0x580 && cp.cob < 0x600)
                    {
                        if (cp.len != 8)
                            return;

                        lock (sdo_queue)
                        {
                            if (SDOcallbacks.ContainsKey(cp.cob))
                            {
                                if (SDOcallbacks[cp.cob].SDOProcess(cp))
                                {
                                    SDOcallbacks.Remove(cp.cob);
                                }
                            }

                            if (sdoevent != null)
                                sdoevent(cp, DateTime.Now);
                        }
                    }

                    //SDO 发送
                    if (cp.cob >= 0x600 && cp.cob < 0x680)
                    {
                        if (sdoevent != null)
                            sdoevent(cp, DateTime.Now);
                    }

                    SDO.kick_SDO();

                    lock (sdo_queue)
                    {
                        if (sdo_queue.Count > 0)
                        {
                            SDO sdoobj = sdo_queue.Peek();

                            if (!SDOcallbacks.ContainsKey((UInt16)(sdoobj.node + 0x580)))
                            {
                                sdoobj = sdo_queue.Dequeue();
                                SDOcallbacks.Add((UInt16)(sdoobj.node + 0x580), sdoobj);
                                sdoobj.sendSDO();
                            }
                        }
                    }

                    //System.Threading.Thread.Sleep(1);
                }
            }

        }

        #region SDOHelpers

        /// 
        /// Write to a node via SDO
        /// 
        /// Node ID
        /// Object Dictionary Index
        /// Object Dictionary sub index
        /// UInt32 data to send
        /// Call back on finished/error event
        /// SDO class that is used to perform the packet handshake, contains error/status codes
        public SDO SDOwrite(byte node, UInt16 Keycode, byte subindex, UInt32 udata, Action<SDO> completedcallback)
        {
            byte[] bytes = BitConverter.GetBytes(udata);
            return SDOwrite(node, Keycode, subindex, bytes, completedcallback);
        }

        /// 
        /// Write to a node via SDO
        /// 
        /// Node ID
        /// Object Dictionary Index
        /// Object Dictionary sub index
        /// Int64 data to send
        /// Call back on finished/error event
        /// SDO class that is used to perform the packet handshake, contains error/status codes
        public SDO SDOwrite(byte node, UInt16 Keycode, byte subindex, Int64 udata, Action<SDO> completedcallback)
        {
            byte[] bytes = BitConverter.GetBytes(udata);
            return SDOwrite(node, Keycode, subindex, bytes, completedcallback);
        }

        /// 
        /// Write to a node via SDO
        /// 
        /// Node ID
        /// Object Dictionary Index
        /// Object Dictionary sub index
        /// UInt64 data to send
        /// Call back on finished/error event
        /// SDO class that is used to perform the packet handshake, contains error/status codes
        public SDO SDOwrite(byte node, UInt16 Keycode, byte subindex, UInt64 udata, Action<SDO> completedcallback)
        {
            byte[] bytes = BitConverter.GetBytes(udata);
            return SDOwrite(node, Keycode, subindex, bytes, completedcallback);
        }

        /// 
        /// Write to a node via SDO
        /// 
        /// Node ID
        /// Object Dictionary Index
        /// Object Dictionary sub index
        /// Int32 data to send
        /// Call back on finished/error event
        /// SDO class that is used to perform the packet handshake, contains error/status codes
        public SDO SDOwrite(byte node, UInt16 Keycode, byte subindex, Int32 udata, Action<SDO> completedcallback)
        {
            byte[] bytes = BitConverter.GetBytes(udata);
            return SDOwrite(node, Keycode, subindex, bytes, completedcallback);
        }

        /// 
        /// Write to a node via SDO
        /// 
        /// Node ID
        /// Object Dictionary Index
        /// Object Dictionary sub index
        /// UInt16 data to send
        /// Call back on finished/error event
        /// SDO class that is used to perform the packet handshake, contains error/status codes
        public SDO SDOwrite(byte node, UInt16 Keycode, byte subindex, Int16 udata, Action<SDO> completedcallback)
        {
            byte[] bytes = BitConverter.GetBytes(udata);
            return SDOwrite(node, Keycode, subindex, bytes, completedcallback);
        }

        /// 
        /// Write to a node via SDO
        /// 
        /// Node ID
        /// Object Dictionary Index
        /// Object Dictionary sub index
        /// UInt16 data to send
        /// Call back on finished/error event
        /// SDO class that is used to perform the packet handshake, contains error/status codes
        public SDO SDOwrite(byte node, UInt16 Keycode, byte subindex, UInt16 udata, Action<SDO> completedcallback)
        {
            byte[] bytes = BitConverter.GetBytes(udata);
            return SDOwrite(node, Keycode, subindex, bytes, completedcallback);
        }

        /// 
        /// Write to a node via SDO
        /// 
        /// Node ID
        /// Object Dictionary Index
        /// Object Dictionary sub index
        /// float data to send
        /// Call back on finished/error event
        /// SDO class that is used to perform the packet handshake, contains error/status codes
        public SDO SDOwrite(byte node, UInt16 Keycode, byte subindex, float ddata, Action<SDO> completedcallback)
        {
            byte[] bytes = BitConverter.GetBytes(ddata);
            return SDOwrite(node, Keycode, subindex, bytes, completedcallback);
        }

        /// 
        /// Write to a node via SDO
        /// 
        /// Node ID
        /// Object Dictionary Index
        /// Object Dictionary sub index
        /// a byte of data to send
        /// Call back on finished/error event
        /// SDO class that is used to perform the packet handshake, contains error/status codes
        public SDO SDOwrite(byte node, UInt16 Keycode, byte subindex, byte udata, Action<SDO> completedcallback)
        {
            byte[] bytes = new byte[1];
            bytes[0] = udata;
            return SDOwrite(node, Keycode, subindex, bytes, completedcallback);
        }

        /// 
        /// Write to a node via SDO
        /// 
        /// Node ID
        /// Object Dictionary Index
        /// Object Dictionary sub index
        /// a byte of unsigned data to send
        /// Call back on finished/error event
        /// SDO class that is used to perform the packet handshake, contains error/status codes
        public SDO SDOwrite(byte node, UInt16 Keycode, byte subindex, sbyte udata, Action<SDO> completedcallback)
        {
            byte[] bytes = new byte[1];
            bytes[0] = (byte)udata;
            return SDOwrite(node, Keycode, subindex, bytes, completedcallback);
        }

        /// 
        /// Write to a node via SDO
        /// 
        /// Node ID
        /// Object Dictionary Index
        /// Object Dictionary sub index
        /// byte[] of data (1-8 bytes) to send
        /// Call back on finished/error event
        /// SDO class that is used to perform the packet handshake, contains error/status codes
        public SDO SDOwrite(byte node, UInt16 Keycode, byte subindex, byte[] data, Action<SDO> completedcallback)
        {

            SDO sdo = new SDO(this, node, Keycode, subindex, SDO.Direction.SDO_WRITE, completedcallback, data);
            lock (sdo_queue)
                sdo_queue.Enqueue(sdo);
            return sdo;
        }

        /// 
        /// Write to a node via SDO
        /// 
        /// Node ID
        /// Object Dictionary Index
        /// Object Dictionary sub index
        /// byte[] of data (1-8 bytes) to send
        /// Call back on finished/error event
        /// SDO class that is used to perform the packet handshake, contains error/status codes
        public SDO SDOwrite(CommunicatinType type,byte node, byte[] data, Action<SDO> completedcallback)
        {
            SDO sdo=null;
            if (data.Length >= 5)
            {
                var Keycode = data[2] * 256 + data[1];
                var subindex = data[3];
                byte[] Databuffer = new byte[data.Length - 4];
                Array.Copy(data,4, Databuffer,0,data.Length-4);
                sdo = new SDO(this, node, (ushort)Keycode, subindex, SDO.Direction.SDO_WRITE, completedcallback, Databuffer);
                sdo.communicatinType = type;
                bool wpsent = false;
                byte cmd = 0;
                sdo.expitided = true;

                switch (Databuffer.Length)
                {
                    case 1:
                        cmd = 0x2f;
                        break;
                    case 2:
                        cmd = 0x2b;
                        break;
                    case 3:
                        cmd = 0x27;
                        break;
                    case 4:
                        cmd = 0x23;
                        break;
                    default:
                        //Bigger than 4 bytes we use segmented transfer
                        cmd = 0x21;
                        sdo.expitided = false;

                        byte[] payload = new byte[4];
                        payload[0] = (byte)Databuffer.Length;
                        payload[1] = (byte)(Databuffer.Length >> 8);
                        payload[2] = (byte)(Databuffer.Length >> 16);
                        payload[3] = (byte)(Databuffer.Length >> 24);

                        sdo.expitideddata = (UInt32)Databuffer.Length;
                        sdo.totaldata = 0;

                        wpsent = true;
                        if (cmd == data[0])
                        {
                            sdo.WriteFlag = sdo.sendpacket(cmd, payload);
                            if (sdo.WriteFlag)
                            {
                                lock (sdo_queue)
                                    sdo_queue.Enqueue(sdo);

                            }
                        }
                        break;
                }

                if (wpsent == false)
                {
                    sdo.WriteFlag = sdo.sendpacket(cmd, Databuffer);
                    if (sdo.WriteFlag)
                    {
                        lock (sdo_queue)
                            sdo_queue.Enqueue(sdo);

                    }
                }
            }
            return sdo;
        }

        /// 
        /// Read from a remote node via SDO
        /// 
        /// Node ID to read from
        /// Object Dictionary Index
        /// Object Dictionary sub index
        /// Call back on finished/error event
        /// SDO class that is used to perform the packet handshake, contains returned data and error/status codes
        public SDO SDOread(byte node, UInt16 Keycode, byte subindex, Action<SDO> completedcallback)
        {
            SDO sdo = new SDO(this, node, Keycode, subindex, SDO.Direction.SDO_READ, completedcallback, null);
            lock (sdo_queue)
                sdo_queue.Enqueue(sdo);
            return sdo;
        }

        /// 
        /// Read from a remote node via SDO
        /// 
        /// Node ID to read from
        /// Object Dictionary Index
        /// Object Dictionary sub index
        /// Call back on finished/error event
        /// SDO class that is used to perform the packet handshake, contains returned data and error/status codes
        public SDO SDOread(CommunicatinType type, byte node, byte[] data, Action<SDO> completedcallback)
        {
            SDO sdo = null;
            if (data.Length == 4)//读取时Data的长度为4
            {
                var Keycode = data[2] * 256 + data[1];
                var subindex = data[3];
                byte[] Databuffer = new byte[4] { 0,0,0,0 };
                //Array.Copy(data, 0, Databuffer, 0, data.Length);
                sdo = new SDO(this, node, (ushort)Keycode, subindex, SDO.Direction.SDO_READ, completedcallback, null);
                sdo.communicatinType = type;
                sdo.ReadFlag = sdo.sendpacket(data[0], Databuffer);
                if (sdo.ReadFlag)
                {
                    lock (sdo_queue)
                        sdo_queue.Enqueue(sdo);
                }
            }
                return sdo;
        }

        /// 
        /// Get the current length of Enqueued items
        /// 
        /// 
        public int getSDOQueueSize()
        {
            return sdo_queue.Count;
        }

        /// 
        /// Flush the SDO queue
        /// 
        public void flushSDOqueue()
        {
            lock (sdo_queue)
                sdo_queue.Clear();
        }

        #endregion

    }
}

六、添加CANopen收发包格式文件

using MCUCommunication.USBCAN.ECAN;
using MCUCommunication.USBCAN.ZLGCAN;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MCUCommunication.Protocols
{
    public class CanPacket
    {
        public UInt16 cob;
        public byte len;
        public byte[] data;
        public bool bridge = false;

        public CanPacket()
        {
        }

        /// 
        /// Construct C# Canpacket from a CanFestival message
        /// 
        /// A CanFestival message struct
        public CanPacket(ZCAN_Receive_Data msg, bool bridge = false)
        {
            //cob = msg.cob_id;
            cob = (ushort)msg.frame.can_id;
            //len = msg.len;
            len = msg.frame.can_dlc;
            data = new byte[len];
            this.bridge = bridge;

            //byte[] temp = BitConverter.GetBytes(msg.frame.data);
            Array.Copy(msg.frame.data, data, len);
        }

        /// 
        /// Construct C# Canpacket from a CanFestival message
        /// 
        /// A CanFestival message struct
        public CanPacket(CAN_OBJ msg, bool bridge = false)
        {
            //cob = msg.cob_id;
            cob = (ushort)msg.ID;
            //len = msg.len;
            len = msg.DataLen;
            data = new byte[len];
            this.bridge = bridge;

            //byte[] temp = BitConverter.GetBytes(msg.frame.data);
            Array.Copy(msg.data, data, len);
        }

        /// 
        /// Convert to a CanFestival message
        /// 
        /// CanFestival message
        public ZCAN_Receive_Data ToMsg()
        {
            ZCAN_Receive_Data msg = new ZCAN_Receive_Data();
            msg.frame.can_id = cob;
            msg.frame.can_dlc = len;

            byte[] temp = new byte[8];
            Array.Copy(data, temp, len);
            msg.frame.data = temp;

            return msg;

        }

        /// 
        /// Convert to a CanFestival message
        /// 
        /// CanFestival message
        public CAN_OBJ ToMsg2()
        {
            CAN_OBJ msg = new CAN_OBJ();
            msg.ID = cob;
            msg.DataLen = len;

            byte[] temp = new byte[8];
            Array.Copy(data, temp, len);
            msg.data = temp;

            return msg;

        }

        /// 
        /// Dump current packet to string
        /// 
        /// Formatted string of current packet
        public override string ToString()
        {
            string output = string.Format("{0:x3} {1:x1}", cob, len);

            for (int x = 0; x < len; x++)
            {
                output += string.Format(" {0:x2}", data[x]);
            }
            return output;
        }
    }
}

七、对象字典封装库

using MVVM;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
using static MCUCommunication.CommunicationHelper;

namespace MCUCommunication.Protocols
{
    public class KeyCodeLib
    {
        public static Dictionary<string, KeyCodeModel> keyCodeTable = new Dictionary<string, KeyCodeModel>();

        private static readonly Lazy<KeyCodeLib> lazy = new Lazy<KeyCodeLib>(() => new KeyCodeLib());
        public static KeyCodeLib Instance => lazy.Value;

        public List<KeyCodeModel> keyCodeModels;

        public KeyCodeLib()
        {
            ReadKeyCodeXml();
            keyCodeModels = GetKeyCodeList();
            //keyCodeModels = GetKeyCodeList();
        }

        #region 读取Xml文件
        private void ReadKeyCodeXml()
        {
            try
            {
                //var Filesname = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "KeyCode\\KeyCodeTable_SerialPort.xml");      //KeyCodeTable_SerialPort.xml SerialPort xml file
                var Filesname = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "KeyCode\\KeyCodeTable_CANopen.xml");           //KeyCodeTable_CANopen.xml CANopen xml file
                FileInfo fileInfo = new FileInfo(Filesname);
                if (!fileInfo.Exists) return;
                else
                {
                    using (var stream = new FileStream(Filesname, FileMode.OpenOrCreate))
                    {
                        var formater = new XmlSerializer(typeof(KeyCodeTable));
                        var reader = new XmlTextReader(stream);
                        var model = formater.Deserialize(reader) as KeyCodeTable;
                        KeyCodeTableModel.KeyCodeTables = model;
                    }
                }
                var KeyCodeTables = KeyCodeTableModel.KeyCodeTables;
                for (int i = 0; i < KeyCodeTables.KeyCode.Count; i++)
                {
                    if (KeyCodeTables.KeyCode[i].SubIndexModel.SubIndex.Count > 0)
                    {
                        //var list = new List();
                        //for (int j = 0; j < KeyCodeTables.KeyCode[i].SubIndexModel.SubIndex.Count; j++)
                        //{
                        //    list.Add(KeyCodeTables.KeyCode[i].SubIndexModel.SubIndex[j]);
                        //}
                        var str1 = JudgeDictionaryDuplicateKey(KeyCodeTables, KeyCodeTables.KeyCode[i].KeyCodeName, i);
                        var str2 = JudgeDictionaryDuplicateValue(KeyCodeTables, KeyCodeTables.KeyCode[i].KeyCodeValue, i);
                        if (str1 != "" || str2 != "") return;
                        keyCodeTable.Add(KeyCodeTables.KeyCode[i].KeyCodeName, new KeyCodeModel(KeyCodeTables.KeyCode[i].KeyCodeName, KeyCodeTables.KeyCode[i].KeyCodeValue, KeyCodeTables.KeyCode[i].SubIndexModel.SubIndex));
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
            
        }
        #endregion

        #region 判断Dictionary重复KeyCodeName
        public string JudgeDictionaryDuplicateKey(KeyCodeTable model,string Key,int index)
        {
            string str = "";
            for (int i = 0; i < model.KeyCode.Count; i++)
            {
                if (Key==model.KeyCode[i].KeyCodeName && index != i)
                {
                    str += $"第{i}{model.KeyCode[i].KeyCodeName}与第{index}{Key}KeyCode名称重复!\n请检查KeyCodeTable.xml是否正确!";
                    MessageBox.Show(str);
                    return str;
                }
            }
            return str;
        }
        #endregion

        #region 判断Dictionary重复KeyCodeValue
        public string JudgeDictionaryDuplicateValue(KeyCodeTable model, string Value, int index)
        {
            string str = "";
            for (int i = 0; i < model.KeyCode.Count; i++)
            {
                if (Value == model.KeyCode[i].KeyCodeValue && index != i)
                {
                    str += $"第{i}{model.KeyCode[i].KeyCodeValue}与第{index}{Value}KeyCode键值重复!\n请检查KeyCodeTable.xml是否正确!";
                    MessageBox.Show(str);
                    return str;
                }
            }
            return str;
        }
        #endregion

        #region 获取KeyCode列表
        //public List GetKeyCodeList()
        //{
        //    var list = new List();
        //    foreach (var item in KeyCodeTable)
        //    {
        //        if (JudyKey(item.Value.CMDHexValue) != "")
        //        {
        //            list.Add(new KeyCodeModel(item.Key, JudyKey(item.Value.CMDHexValue), 0));
        //        }
        //        else
        //        {
        //            list.Add(new KeyCodeModel(item.Key, item.Value.CMDHexValue, 0));
        //        }
        //    }
        //    return list;
        //}
        public List<KeyCodeModel> GetKeyCodeList()
        {
            var list = new List<KeyCodeModel>();
            foreach (var item in keyCodeTable)
            {
                list.Add(new KeyCodeModel(item.Key, item.Value.CMDHexValue, item.Value.SubIndex));
            }
            return list;
        }
        #endregion

        #region 终端--根据部分获取KeyCode
        public List<string> GetKeyCode(string someCmd)
        {
            //var list = GetKeyCodeList();
            var tarlist = new List<string>();

            foreach (var item in keyCodeModels)
            {
                if (item.CMDstring.ToLower().Trim().Contains(someCmd.Trim().ToLower()))
                {
                    tarlist.Add(item.CMDstring);
                }
            }
            return tarlist;
        }
        #endregion

        #region 判断重复键值
        public string JudyKey(string value)
        {
            var tarStr = "";
            foreach (var item in keyCodeTable)
            {
                if (value == item.Key)
                {
                    tarStr = item.Value.CMDHexValue;
                }
            }
            return tarStr;
        }
        #endregion

        #region 判断空值Data
        public byte[] JudyData(byte[] value)
        {
            return value;
        }
        #endregion

        #region 获取KeyCode的值
        public string GetKeyCodeNameFromHexStr(string HexkeyCode)
        {
            string str = "";
            foreach (var item in keyCodeModels)
            {
                if (HexkeyCode != null && item.CMDHexValue.ToLower().Trim() == HexkeyCode.ToLower().Trim())
                {
                    str = item.CMDstring;
                }
            }
            return str;
        }
        public string GetKeyCodeValue(string keyCode)
        {
            var tarStr = "";
            //var list = GetKeyCodeList();
            foreach (var item in keyCodeModels)
            {
                if (keyCode!=null && item.CMDstring.ToLower().Trim() == keyCode.ToLower().Trim())//转换为小写在判断
                {
                    tarStr = item.CMDHexValue;
                }
            }
            return tarStr;
        }

        public byte[] GetKeyCodeHexValue(string keyCode)
        {
            byte[] byteArray = new byte[2];
            //var list = GetKeyCodeList();
            foreach (var item in keyCodeModels)
            {
                if (keyCode != null && item.CMDstring.ToLower().Trim() == keyCode.ToLower().Trim())
                {
                    //小端
                    byteArray[1] = (byte)(Convert.ToInt32(item.CMDHexValue, 16) / 256);
                    byteArray[0] = (byte)(Convert.ToInt32(item.CMDHexValue, 16) % 256);
                }
            }
            return byteArray;//不满足则返回0
        }

        //public string GetKeyCodeHexValueStr(string keyCode)
        //{
        //    string byteArray = "";
        //    //var list = GetKeyCodeList();
        //    foreach (var item in keyCodeModels)
        //    {
        //        if (keyCode != null && item.CMDHexValue.ToLower().Trim() == keyCode.ToLower().Trim())
        //        {
        //            byteArray = 
        //        }
        //    }
        //    return byteArray;//不满足则返回0
        //}
        #endregion

        #region 获取数值大小
        public int GetKeyCodeListValueSizeFromKeyCodeStr(string keyCode,int index)
        {
            int str = 0;
            foreach (var item in keyCodeModels)
            {
                if (keyCode != null && item.CMDstring.ToLower().Trim() == keyCode.ToLower().Trim())
                {
                    if (index + 1 > item.SubIndex.Count) return 0;
                    else
                    {
                        str = item.SubIndex[index].Size;
                    }
                }
            }
            return str;
        }
        #endregion

        #region 填充Data
        public void SetData(string keycode,int index,int Package)
        {
            foreach (var item in keyCodeModels)
            {
                if (item.CMDHexValue.ToLower().Trim() == keycode.ToLower().Trim())
                {
                    foreach (var sub in item.SubIndex)
                    {
                        if (index==sub.Index)
                        {
                            sub.Value = BitConverter.GetBytes(Package);
                        }
                    }
                }
            }
        }
        #endregion

        #region 获取Data
        public int GetData(string keycode,int index)
        {
            int array=0;
            foreach (var item in keyCodeModels)
            {
                if (item.CMDHexValue.ToLower().Trim() == keycode.ToLower().Trim())
                {
                    foreach (var sub in item.SubIndex)
                    {
                        if (index == sub.Index)
                        {
                            for (int i = 0; i < sub.Value.Length; i++)
                            {
                                array += (sub.Value[i] << (8 * i));
                            }
                        }
                    }
                }
            }
            return array;
        }
        #endregion

    }

    public class KeyCodeModel : ModelBase
    {
        private string cMDstring;
        public string CMDstring
        {
            get { return cMDstring; }
            set
            {
                cMDstring = value;
                RaisePropertyChanged(() => CMDstring);
            }
        }

        private string cMDHexValue;
        public string CMDHexValue
        {
            get { return cMDHexValue; }
            set
            {
                cMDHexValue = value;
                RaisePropertyChanged(() => CMDHexValue);
            }
        }


        private List<SubIndex> subIndex;
        public List<SubIndex> SubIndex
        {
            get { return subIndex; }
            set
            {
                subIndex = value;
                if (subIndex.Count > 0)
                {
                    for (int i = 0; i < SubIndex.Count; i++)
                    {
                        vs.Add(subIndex[i].Index.ToString());
                        describelist.Add(subIndex[i].Description);
                        typelist.Add(subIndex[i].Type);
                        sizelist.Add(subIndex[i].Size);
                        readWritelist.Add(subIndex[i].ReadWrite);
                        saveToFlashlist.Add(subIndex[i].SaveToFlash);
                        funcCbklist.Add(subIndex[i].FuncCbk);
                        maxlist.Add(subIndex[i].Max);
                        minlist.Add(subIndex[i].Min);
                    }
                }
                RaisePropertyChanged(() => SubIndex);
            }
        }

        private List<string> subIndexModelIndex;
        public List<string> SubIndexModelIndex
        {
            get { return vs; }
            set
            {
                subIndexModelIndex = value;
                RaisePropertyChanged(() => SubIndexModelIndex);
            }
        }

        private List<string> description;
        public List<string> Description
        {
            get { return describelist; }
            set
            {
                description = value;
                RaisePropertyChanged(() => Description);
            }
        }

        private List<string> type;
        public List<string> Type
        {
            get { return typelist; }
            set
            {
                type = value;
                RaisePropertyChanged(() => Type);
            }
        }

        private List<int> size;
        public List<int> Size
        {
            get { return sizelist; }
            set
            {
                size = value;
                RaisePropertyChanged(() => Size);
            }
        }

        private List<string> readWrite;
        public List<string> ReadWrite
        {
            get { return readWritelist; }
            set
            {
                readWrite = value;
                RaisePropertyChanged(() => ReadWrite);
            }
        }

        private List<string> saveToFlash;
        public List<string> SaveToFlash
        {
            get { return saveToFlashlist; }
            set
            {
                saveToFlash = value;
                RaisePropertyChanged(() => SaveToFlash);
            }
        }

        private List<string> funcCbk;
        public List<string> FuncCbk
        {
            get { return funcCbklist; }
            set
            {
                funcCbk = value;
                RaisePropertyChanged(() => FuncCbk);
            }
        }

        private List<int> max;
        public List<int> Max
        {
            get { return maxlist; }
            set
            {
                max = value;
                RaisePropertyChanged(() => Max);
            }
        }

        private List<int> min;
        public List<int> Min
        {
            get { return minlist; }
            set
            {
                min = value;
                RaisePropertyChanged(() => Min);
            }
        }

        List<string> vs = new List<string>();
        List<string> describelist = new List<string>();
        List<string> typelist = new List<string>();
        List<int> sizelist = new List<int>();
        List<string> readWritelist = new List<string>();
        List<string> saveToFlashlist = new List<string>();
        List<string> funcCbklist = new List<string>();
        List<int> maxlist = new List<int>();
        List<int> minlist = new List<int>();

        public KeyCodeModel(string cmdString, string cmdHexValue, List<SubIndex> subindex)
        {
            CMDstring = cmdString;
            CMDHexValue = cmdHexValue;
            SubIndex = subindex;
        }
    }
}

八、对象字典XML文件序列化文件

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace MCUCommunication.Protocols
{
    public class KeyCodeTableModel
    {
        public static KeyCodeTable KeyCodeTables { get; set; }

        public KeyCodeTableModel()
        {
            KeyCodeTables = new KeyCodeTable();
        }
    }

    [Serializable]
    [XmlRoot("KeyCodeTable")]
    public class KeyCodeTable
    {
        [XmlElement("KeyCode")]
        public List<KeyCode> KeyCode { get; set; }

    }

    [Serializable]
    public class KeyCode
    {
        public string KeyCodeName { get; set; }
        public string KeyCodeValue { get; set; }
        [XmlElement("SubIndexModel")]
        public SubIndexModel SubIndexModel { get; set; }

    }

    [Serializable]
    public class SubIndexModel
    {
        [XmlElement("SubIndex")]
        public List<SubIndex> SubIndex { get; set; }

    }

    [Serializable]
    public class SubIndex
    {
        public int Index { get; set; }
        public string Type { get; set; }
        public int Size { get; set; }
        public byte[] Value { get; set; }
        public string ReadWrite { get; set; }
        public string SaveToFlash { get; set; }
        public string FuncCbk { get; set; }
        public int Max { get; set; }
        public int Min { get; set; }
        //public int DefautValue { get; set; }
        public string Description { get; set; }

    }


}

九、SDO文件

using MCUCommunication.Protocols;
using MCUCommunication.USBCAN.ZLGCAN;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static MCUCommunication.CommunicationHelper;

namespace DCMotorControlSystem.Commoms.Protocols
{
    public delegate void DownLoadDelegate(byte[] buffer);
    public delegate void DownLoadScheduleDelegate(float percentage);

    public class SDO
    {
        /// 
        /// Direction of the SDO transfer
        /// 
        public enum Direction
        {
            SDO_READ = 0,           //读
            SDO_WRITE = 1,          //写
        }

        /// 
        /// Possible SDO states used by simple handshake statemachine
        /// 
        public enum SDO_STATE
        {
            SDO_INIT,            //初始化
            SDO_SENT,            //发送
            SDO_HANDSHAKE,       //握手
            SDO_FINISHED,        //完成
            SDO_ERROR,           //错误
        }

        /// 
        /// Expitided data buffer, if transfer is 4 bytes or less, its here
        /// 

        public readonly byte node;

        public CommunicatinType communicatinType;

        //FIX me i was using all these from outside this class
        //should see if that is really needed or if better accessors are required
        //may be readonly access etc.
        public byte[] databuffer = null;
        public SDO_STATE state;
        public UInt16 index;
        public byte subindex;
        public UInt32 expitideddata;
        public bool expitided = false;
        public bool ReadFlag;
        public bool WriteFlag;

        static List<SDO> activeSDO = new List<SDO>();

        private Action<SDO> completedcallback;

        private Direction dir;

        public UInt32 totaldata;
        private CanOpenSDOLib can;
        public bool lasttoggle = false;
        private DateTime timeout;
        private ManualResetEvent finishedevent;

        public static event DownLoadDelegate downLoadDelegateEvent;
        public static event DownLoadScheduleDelegate downLoadScheduleEvent;
        //private debuglevel dbglevel;

        /// 
        /// Construct a new SDO object
        /// 
        /// a libCanopenSimple object that will give access to the hardware
        /// The note to talk to (UInt16)
        /// The index in the object dictionary to access
        /// The subindex in the object dictionary to access
        /// Direction of transfer
        /// Optional, completed callback (or null if not required)
        /// A byte array of data to be transfered to or from if more than 4 bytes
        public SDO(CanOpenSDOLib can, byte node, UInt16 index, byte subindex, Direction dir, Action<SDO> completedcallback, byte[] databuffer)
        {
            this.can = can;
            this.index = index;
            this.subindex = subindex;
            this.node = node;
            this.dir = dir;
            this.completedcallback = completedcallback;
            this.databuffer = databuffer;

            finishedevent = new ManualResetEvent(false);
            state = SDO_STATE.SDO_INIT;
            //dbglevel = can.dbglevel;

        }

        /// 
        /// Add this SDO object to the active list
        /// 
        public void sendSDO()
        {
            lock (activeSDO)
                activeSDO.Add(this);
        }

        public static bool isEmpty()
        {
            return activeSDO.Count == 0;
        }

        /// 
        /// Has the SDO transfer finished?
        /// 
        /// True if the SDO has finished and fired its finished event
        public bool WaitOne()
        {
            return finishedevent.WaitOne();
        }


        /// 
        /// SDO pump, call this often
        /// 
        public static void kick_SDO()
        {
            List<SDO> tokill = new List<SDO>();

            lock (activeSDO)
            {
                foreach (SDO s in activeSDO)
                {
                    //s.kick_SDOp();
                    if (s.state == SDO_STATE.SDO_FINISHED || s.state == SDO_STATE.SDO_ERROR)
                    {
                        tokill.Add(s);
                    }
                }
            }

            foreach (SDO s in tokill)
            {
                lock (activeSDO)
                    activeSDO.Remove(s);
                s.SDOFinish();
            }
        }

        /// 
        /// State machine for a specific SDO instance
        /// 
        public void kick_SDOp()
        {
            if (state != SDO_STATE.SDO_INIT && DateTime.Now > timeout)
            {
                state = SDO_STATE.SDO_ERROR;

                Console.WriteLine("SDO Timeout Error on {0:x4}/{1:x2} {2:x8}", this.index, this.subindex, expitideddata);

                if (completedcallback != null)
                    completedcallback(this);

                return;
            }

            if (state == SDO_STATE.SDO_INIT)
            {
                timeout = DateTime.Now + new TimeSpan(0, 0, 5);
                state = SDO_STATE.SDO_SENT;

                if (dir == Direction.SDO_READ)
                {
                    byte cmd = 0x40;
                    byte[] payload = new byte[4] { 0, 0, 0, 0 };
                    sendpacket(cmd, payload);
                }

                if (dir == Direction.SDO_WRITE)
                {
                    bool wpsent = false;
                    byte cmd = 0;

                    expitided = true;

                    switch (databuffer.Length)
                    {
                        case 1:
                            cmd = 0x2f;
                            break;
                        case 2:
                            cmd = 0x2b;
                            break;
                        case 3:
                            cmd = 0x27;
                            break;
                        case 4:
                            cmd = 0x23;
                            break;
                        default:
                            //Bigger than 4 bytes we use segmented transfer
                            cmd = 0x21;
                            expitided = false;

                            byte[] payload = new byte[4];
                            payload[0] = (byte)databuffer.Length;
                            payload[1] = (byte)(databuffer.Length >> 8);
                            payload[2] = (byte)(databuffer.Length >> 16);
                            payload[3] = (byte)(databuffer.Length >> 24);

                            expitideddata = (UInt32)databuffer.Length;
                            totaldata = 0;

                            wpsent = true;
                            sendpacket(cmd, payload);
                            break;

                    }

                    if (wpsent == false)
                        sendpacket(cmd, databuffer);

                }
            }
        }

        /// 
        /// Send a SDO packet, with command and payload, should be only called from SDO state machine
        /// 
        /// SDO command byte
        /// Data payload to send
        public bool sendpacket(byte cmd, byte[] payload)
        {

            CanPacket p = new CanPacket();
            p.cob = (UInt16)(0x600 + node);
            p.len = 8;
            p.data = new byte[8];
            p.data[0] = cmd;
            p.data[1] = (byte)index;
            p.data[2] = (byte)(index >> 8);
            p.data[3] = subindex;

            int sendlength = 4;

            if (payload.Length < 4)
                sendlength = payload.Length;

            for (int x = 0; x < sendlength; x++)
            {
                p.data[4 + x] = payload[x];
            }

            //if (dbglevel == debuglevel.DEBUG_ALL)
                //Console.WriteLine(String.Format("Sending a new SDO packet: {0}", p.ToString()));

            return can.SendPacket(communicatinType,p);
        }

        /// 
        /// Send a SDO packet, with command and payload, should be only called from SDO state machine
        /// 
        /// SDO command byte
        /// Data payload to send
        public bool sendpacket(byte cmd, byte[] payload, ushort index, byte subindex)
        {

            CanPacket p = new CanPacket();
            p.cob = (UInt16)(0x600 + node);
            p.len = 8;
            p.data = new byte[8];
            p.data[0] = cmd;
            p.data[1] = (byte)index;
            p.data[2] = (byte)(index >> 8);
            p.data[3] = subindex;

            int sendlength = 4;

            if (payload.Length < 4)
                sendlength = payload.Length;

            for (int x = 0; x < sendlength; x++)
            {
                p.data[4 + x] = payload[x];
            }

            //if (dbglevel == debuglevel.DEBUG_ALL)
            //Console.WriteLine(String.Format("Sending a new SDO packet: {0}", p.ToString()));

            return can.SendPacket(communicatinType, p);
        }

        /// 
        /// Segmented transfer update function, should be only called from SDO state machine
        /// 
        /// SDO command byte
        /// Data payload
        private bool sendpacketsegment(byte cmd, byte[] payload)
        {
            CanPacket p = new CanPacket();
            p.cob = (UInt16)(0x600 + node);
            p.len = 8;
            p.data = new byte[8];
            p.data[0] = cmd;

            for (int x = 0; x < payload.Length; x++)
            {
                p.data[1 + x] = payload[x];
            }

            //if (dbglevel == debuglevel.DEBUG_ALL)
                //Console.WriteLine(String.Format("Sending a new segmented SDO packet: {0}", p.ToString()));

            return can.SendPacket(communicatinType, p);
        }

        /// 
        /// Force finish the SDO and trigger its finished event
        /// 
        public void SDOFinish()
        {
            can.SDOcallbacks.Remove((UInt16)(this.node + 0x580));
            finishedevent.Set();
        }

        /// 
        /// SDO Instance processor, process current SDO reply and decide what to do next
        /// 
        /// SDO Canpacket to process
        /// 
        public bool SDOProcess(CanPacket cp)
        {

            int SCS = cp.data[0] >> 5; //7-5

            int n = (0x03 & (cp.data[0] >> 2)); //3-2 data size for normal packets
            int e = (0x01 & (cp.data[0] >> 1)); // expidited flag
            int s = (cp.data[0] & 0x01); // data size set flag

            int sn = (0x07 & (cp.data[0] >> 1)); //3-1 data size for segment packets
            int t = (0x01 & (cp.data[0] >> 4));  //toggle flag

            int c = 0x01 & cp.data[0]; //More segments to upload?

            //ERROR abort
            if (SCS == 0x04)
            {

                expitideddata = (UInt32)(cp.data[4] + (cp.data[5] << 8) + (cp.data[6] << 16) + (cp.data[7] << 24));
                databuffer = BitConverter.GetBytes(expitideddata);

                state = SDO_STATE.SDO_ERROR;

                Console.WriteLine("SDO Error on {0:x4}/{1:x2} {2:x8}", this.index, this.subindex, expitideddata);

                if (completedcallback != null)
                    completedcallback(this);

                return true;
            }

            //Write complete
            if (SCS == 0x03)
            {
                UInt16 index = (UInt16)(cp.data[1] + (cp.data[2] << 8));
                byte sub = cp.data[3];

                int node = cp.cob - 0x580;


                foreach (SDO sdo in activeSDO)
                {
                    if (sdo.node == node)
                    {
                        if ((index == sdo.index && sub == sdo.subindex)) //if segments break its here
                        {
                            if (expitided == false)
                            {
                                state = SDO_STATE.SDO_HANDSHAKE;
                                requestNextSegment(false);
                                return false;
                            }
                        }

                    }
                }

                state = SDO_STATE.SDO_FINISHED;

                if (completedcallback != null)
                    completedcallback(this);

                return true;
            }

            //Write segment complete
            if (SCS == 0x01)
            {
                if (totaldata < expitideddata)
                {
                    lasttoggle = !lasttoggle;
                    requestNextSegment(lasttoggle);
                }
                else
                {
                    state = SDO_STATE.SDO_FINISHED;
                    if (completedcallback != null)
                        completedcallback(this);
                }

            }

            //if expedited just handle the data
            if (SCS == 0x02 && e == 1)
            {
                //Expidited and length are set so its a regular short transfer

                expitideddata = (UInt32)(cp.data[4] + (cp.data[5] << 8) + (cp.data[6] << 16) + (cp.data[7] << 24));
                databuffer = BitConverter.GetBytes(expitideddata);

                state = SDO_STATE.SDO_FINISHED;

                if (completedcallback != null)
                {
                    try
                    {
                        completedcallback(this);
                    }
                    catch { }

                }
                return true;
            }

            if (SCS == 0x02)
            {
                UInt32 count = (UInt32)(cp.data[4] + (cp.data[5] << 8) + (cp.data[6] << 16) + (cp.data[7] << 24));

                Console.WriteLine("RX Segmented transfer start length is {0}", count);
                expitideddata = count;
                databuffer = new byte[expitideddata];
                totaldata = 0;
                //Request next segment

                if (dir==Direction.SDO_WRITE)
                {
                    dir = Direction.SDO_READ;//这里有一个Bug,但目前上位机只需要上载协议,没有下载,暂时先这么修改 20220531,后期完善
                }

                requestNextSegment(false); //toggle off on first request
                return false;

            }

            //segmented transfer
            UInt32 scount = (UInt32)(7 - sn);

            //Segments toggle on
            if (SCS == 0x00)
            {

                Console.WriteLine("RX Segmented transfer update length is {0} -- {1}", scount, totaldata);

                if (downLoadScheduleEvent!=null)
                {
                    downLoadScheduleEvent((totaldata+ scount )/ (float)databuffer.Length);
                    Console.WriteLine("{0}", (totaldata + scount) / (float)databuffer.Length);
                }
                
                for (int x = 0; x < scount; x++)
                {
                    if ((totaldata + x) < databuffer.Length)
                        databuffer[totaldata + x] = cp.data[1 + x];
                }

                totaldata += 7;

                if ((totaldata < expitideddata) && c == 0)
                {
                    lasttoggle = !lasttoggle;
                    requestNextSegment(lasttoggle);
                }
                else
                {
                    state = SDO_STATE.SDO_FINISHED;
                    if (completedcallback != null)
                        completedcallback(this);

                    if (downLoadDelegateEvent != null)
                    {
                        downLoadDelegateEvent(databuffer);
                    }
                }

            }

            return false;
        }

        /// 
        /// Request the next segment in a segmented transfet
        /// 
        /// Segmented transfer toggle flag, should alternate for each successive transfer
        private void requestNextSegment(bool toggle)
        {

            timeout = DateTime.Now + new TimeSpan(0, 0, 5);

            if (dir == Direction.SDO_READ)
            {
                byte cmd = 0x60;//0x61
                if (toggle)
                    cmd |= 0x10;

                sendpacket(cmd, new byte[4],0,0);
            }
            else
            {
                byte cmd = 0x00;
                if (toggle)
                    cmd |= 0x10;

                int bytecount = (int)(databuffer.Length - totaldata); //11 - 7
                byte[] nextdata;
                if (bytecount >= 7)
                {
                    bytecount = 7;
                }


                nextdata = new byte[bytecount];

                for (int x = 0; x < bytecount; x++)
                {
                    if (databuffer.Length > (totaldata + x))
                        nextdata[x] = databuffer[totaldata + x];

                }

                if (totaldata + 7 >= databuffer.Length)
                {
                    cmd |= 0x01; //END of packet sequence
                }

                if (bytecount != 7)
                {
                    int n = 7 - bytecount;
                    n = n << 1;
                    cmd |= (byte)n;
                }

                sendpacketsegment(cmd, nextdata);
                totaldata += (uint)bytecount;
            }

        }
    }
}

十、SDOErr文件

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MCUCommunication.Protocols
{
    public class SDOErr
    {
        private static readonly Lazy<SDOErr> lazy = new Lazy<SDOErr>(() => new SDOErr());
        public static SDOErr Instance => lazy.Value;

        #region SDO错误字典
        public static Dictionary<string, string> SDOErrTable = new Dictionary<string, string>
        {
            {"05030000","触发位没有交替改变"},
            {"05040000","SDO协议超时"},
            {"05040001","非法或未知的Client/Server命令字"},
            {"05040002","无效的块大小(仅Block Transfer模式"},
            {"05040003","无效的序号(仅Block Transfer模式"},
            {"05040004","CRC错误(仅Block Transfer模式"},
            {"05040005","内存溢出" },
            {"06010000","对象不支持访问" },
            {"06010001","试图读只写对象" },
            {"06010002","试图写只读对象" },
            {"06020000","对象字典中对象不存在" },
            {"06040041","对象不能够映射到PDO" },
            {"06040042","映射的对象的数目和长度超出PDO长度" },
            {"06040043","一般性参数不兼容" },
            {"06040047","一般性设备内部不兼容" },
            {"06060000","硬件错误导致对象访问失败" },
            {"06070010","数据类型不匹配,服务参数长度不匹配" },
            {"06070012","数据类型不匹配,服务参数长度太大" },
            {"06070013","数据类型不匹配,服务参数长度太短" },
            {"06090011","子索引不存在" },
            {"06090030","超出参数的值范围(写访问)" },
            {"06090031","写入参数数值太大" },
            {"06090032","写入参数数值太小" },
            {"06090036","最大值小于最小值" },
            {"060A0023","资源不可用:SDO连接" },
            {"08000000","一般性错误" },
            {"08000020","数据不能传送或保存到应用" },
            {"08000021","由于本地控制导致数据不能传送或保存到应用" },
            {"08000022","由于当前设备状态导致数据不能传送或保存到应用" },
            {"08000023","对象字典动态产生错误或对象字典不存在" },
        };
        #endregion

        #region 构造函数
        public SDOErr()
        {

        }
        #endregion

        #region 方法
        #region 通过错误码查询错误描述
        public string GetSDOErrSubscribersFormErrNum(string ErrNum)
        {
            if (SDOErrTable.ContainsKey(ErrNum))
            {
                return SDOErrTable[ErrNum];
            }
            else
            {
                return null;
            }   
        }
        #endregion
        #endregion
    }
}

基本写完了,最后只需要先调用CANDeviceStart(),打开CAN,然后调用CANDataSend,进行发送数据。如果接收到数据,USBCANReceiveData接收委托事件关联就会响应。

你可能感兴趣的:(CANopen,WPF,c#,开发语言,mcu)