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设备开启
/// <summary>
/// CAN设备开启
/// </summary>
/// <returns>设备是否开启</returns>
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库文件
还需要注意dll库兼容的是64位或32位系统(若出现无法读取dll文件的情况,大概率就是dll与系统不匹配的问题)
五、添加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()
{
}
/// <summary>
/// Send a Can packet on the bus
/// </summary>
/// <param name="p"></param>
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;
}
/// <summary>
/// Recieved message callback handler
/// </summary>
/// <param name="msg">CanOpen message recieved from the bus</param>
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);
}
}
/// <summary>
/// Recieved message callback handler
/// </summary>
/// <param name="msg">CanOpen message recieved from the bus</param>
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();
}
/// <summary>
/// Close the CanOpen CanFestival driver
/// </summary>
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
/// <summary>
/// Write to a node via SDO
/// </summary>
/// <param name="node">Node ID</param>
/// <param name="index">Object Dictionary Index</param>
/// <param name="subindex">Object Dictionary sub index</param>
/// <param name="udata">UInt32 data to send</param>
/// <param name="completedcallback">Call back on finished/error event</param>
/// <returns>SDO class that is used to perform the packet handshake, contains error/status codes</returns>
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);
}
/// <summary>
/// Write to a node via SDO
/// </summary>
/// <param name="node">Node ID</param>
/// <param name="index">Object Dictionary Index</param>
/// <param name="subindex">Object Dictionary sub index</param>
/// <param name="udata">Int64 data to send</param>
/// <param name="completedcallback">Call back on finished/error event</param>
/// <returns>SDO class that is used to perform the packet handshake, contains error/status codes</returns>
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);
}
/// <summary>
/// Write to a node via SDO
/// </summary>
/// <param name="node">Node ID</param>
/// <param name="index">Object Dictionary Index</param>
/// <param name="subindex">Object Dictionary sub index</param>
/// <param name="udata">UInt64 data to send</param>
/// <param name="completedcallback">Call back on finished/error event</param>
/// <returns>SDO class that is used to perform the packet handshake, contains error/status codes</returns>
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);
}
/// <summary>
/// Write to a node via SDO
/// </summary>
/// <param name="node">Node ID</param>
/// <param name="index">Object Dictionary Index</param>
/// <param name="subindex">Object Dictionary sub index</param>
/// <param name="udata">Int32 data to send</param>
/// <param name="completedcallback">Call back on finished/error event</param>
/// <returns>SDO class that is used to perform the packet handshake, contains error/status codes</returns>
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);
}
/// <summary>
/// Write to a node via SDO
/// </summary>
/// <param name="node">Node ID</param>
/// <param name="index">Object Dictionary Index</param>
/// <param name="subindex">Object Dictionary sub index</param>
/// <param name="udata">UInt16 data to send</param>
/// <param name="completedcallback">Call back on finished/error event</param>
/// <returns>SDO class that is used to perform the packet handshake, contains error/status codes</returns>
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);
}
/// <summary>
/// Write to a node via SDO
/// </summary>
/// <param name="node">Node ID</param>
/// <param name="index">Object Dictionary Index</param>
/// <param name="subindex">Object Dictionary sub index</param>
/// <param name="udata">UInt16 data to send</param>
/// <param name="completedcallback">Call back on finished/error event</param>
/// <returns>SDO class that is used to perform the packet handshake, contains error/status codes</returns>
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);
}
/// <summary>
/// Write to a node via SDO
/// </summary>
/// <param name="node">Node ID</param>
/// <param name="index">Object Dictionary Index</param>
/// <param name="subindex">Object Dictionary sub index</param>
/// <param name="udata">float data to send</param>
/// <param name="completedcallback">Call back on finished/error event</param>
/// <returns>SDO class that is used to perform the packet handshake, contains error/status codes</returns>
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);
}
/// <summary>
/// Write to a node via SDO
/// </summary>
/// <param name="node">Node ID</param>
/// <param name="index">Object Dictionary Index</param>
/// <param name="subindex">Object Dictionary sub index</param>
/// <param name="udata">a byte of data to send</param>
/// <param name="completedcallback">Call back on finished/error event</param>
/// <returns>SDO class that is used to perform the packet handshake, contains error/statu