Files
CapMachine/Sample/C#_USBCANFD_251215/USBCANFD/Program.cs
2026-02-02 21:22:01 +08:00

930 lines
41 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using ZLGAPI;
namespace USBCANFD
{
internal class Program
{
public static IntPtr devHandle = IntPtr.Zero; // 设备句柄
public static IntPtr[] chnHandle = new IntPtr[10]; // 通道句柄
public static int chn_max = 2; // 最大通道数量
public static volatile bool isRunning = true; // 线程标志
public static uint isMerge = 0; // 合并接收标志
public static uint isBusload = 0; // 通道利用率使能
// 线程传递参数
class ThreadParams
{
public IntPtr dev { get; set; } // 设备句柄
public IntPtr chn { get; set; } // 通道句柄
public int chnNum { get; set; } // 通道号
}
class ThreadBusload
{
public IntPtr dev { get; set; } // 设备句柄
public int chnNum { get; set; } // 通道号
}
static void Main(string[] args)
{
uint ret; // 返回值
// 打开设备 方式一:通过索引顺序打开设备
Program.devHandle = ZLGCAN.ZCAN_OpenDevice(ZLGCAN.ZCAN_USBCANFD_200U, 0, 0);
if (Program.devHandle == IntPtr.Zero)
{
Console.WriteLine("打开设备失败");
Console.ReadKey();
return;
}
else
Console.WriteLine("打开设备成功");
// 打开设备 方式二通过自定义序列号打开设备需要首先执行SN_test函数设置自定义序列号
//Program.devHandle = ZLGCAN.ZCAN_OpenDeviceByName(ZLGCAN.ZCAN_USBCANFD_200U, "C#01");
//if (Program.devHandle == IntPtr.Zero)
//{
// Console.WriteLine("打开设备失败");
// Console.ReadKey();
// return;
//}
//else
// Console.WriteLine("打开设备成功");
//// 获取设备信息
//ZLGCAN.ZCAN_DEVICE_INFO info = new ZLGCAN.ZCAN_DEVICE_INFO();
//IntPtr P2Info = Marshal.AllocHGlobal(Marshal.SizeOf(info));
//if (1 == ZLGCAN.ZCAN_GetDeviceInf(Program.devHandle, P2Info))
//{
// info = (ZLGCAN.ZCAN_DEVICE_INFO)Marshal.PtrToStructure(P2Info, typeof(ZLGCAN.ZCAN_DEVICE_INFO));
// Console.WriteLine("设备型号 {0},设备固件版本 {1:X}", new string(info.str_hw_Type).TrimEnd('\0'), info.fw_Version);
//}
//// 自定义序列号
//SN_test(Program.devHandle);
// 该函数用于检测设备是否在线
//uint status = ZLGCAN.ZCAN_IsDeviceOnLine(Program.devHandle);
//Console.WriteLine("设备状态{0}",
// status == 0 ? "ERR" :
// status == 1 ? "OK" :
// status == 2 ? "ONLINE" :
// status == 3 ? "OFFLINE" :
// status == 4 ? "UNSUPPORTED" :
// status == 5 ? "BUFFER_TOO_SMALL" : "UNKNOW");
// 初始化并打开通道
for (int i = 0; i < Program.chn_max; i++)
{
Program.chnHandle[i] = Init_chn_USBCANFD(Program.devHandle, i); // 必须初始化完一个通道,再走另外一个通道!!!
if (Program.chnHandle[i] == IntPtr.Zero)
return;
}
// 接收线程处理
if (Program.isMerge == 1) // 合并接收
{
// 设置合并接收
ret = ZLGCAN.ZCAN_SetValue(Program.devHandle, "0/set_device_recv_merge", "1");
if (ret != 1)
{
Console.WriteLine("设置合并接收失败");
Console.ReadKey();
}
ThreadParams parameters = new ThreadParams
{
dev = Program.devHandle,
chnNum = 0
};
Thread workerThread = new Thread(ReceiveThread_Merge);
workerThread.IsBackground = true; // 设置为后台线程不需要join
workerThread.Start(parameters); // 启动线程
}
else // 普通接收
{
// 关闭合并接收
ret = ZLGCAN.ZCAN_SetValue(Program.devHandle, "0/set_device_recv_merge", "0");
if (ret != 1)
{
Console.WriteLine("关闭合并接收失败");
Console.ReadKey();
}
for (int i = 0; i < Program.chn_max; i++)
{
ThreadParams parameters = new ThreadParams
{
chn = Program.chnHandle[i],
chnNum = i
};
Thread workerThread = new Thread(ReceiveThread); // 每通道独立线程接收
workerThread.IsBackground = true; // 设置为后台线程不需要join
workerThread.Start(parameters); // 启动线程
}
}
if (Program.isBusload == 1) // 通道利用率
{
ThreadBusload Busload = new ThreadBusload
{
dev = Program.devHandle,
chnNum = 0
};
Thread ThreadBusload = new Thread(Thread_Busload);
ThreadBusload.IsBackground = true; // 设置为后台线程不需要join
ThreadBusload.Start(Busload); // 启动线程
}
// 发送示例
Send_test(Program.chnHandle[0]);
//Send_test_Merge(Program.devHandle);
//// 定时发送
//AutoSend_test(Program.devHandle, 0);
//// 队列发送
//QueueSend_test(Program.devHandle, Program.chnHandle[0]);
// 阻塞等待
Console.ReadKey();
Program.isRunning = false;
// 关闭通道
for (int i = 0; i < Program.chn_max; i++)
{
ret = ZLGCAN.ZCAN_ResetCAN(Program.chnHandle[i]);
if (ret != 1)
Console.WriteLine("关闭通道{0}失败", i);
}
// 关闭设备
ret = ZLGCAN.ZCAN_CloseDevice(Program.devHandle);
if (ret != 1)
{
Console.WriteLine("关闭设备失败");
Console.ReadKey();
return;
}
}
// 初始化CAN通道
static IntPtr Init_chn_USBCANFD(IntPtr dev, int chn_idx)
{
IntPtr temp = IntPtr.Zero;
uint ret; // 返回值
// 仲裁域波特率
string path = String.Format("{0}/canfd_abit_baud_rate", chn_idx);
ret = ZLGCAN.ZCAN_SetValue(dev, path, "500000"); // 500k
if (ret != 1)
{
Console.WriteLine("设置仲裁域波特率失败");
Console.ReadKey();
return IntPtr.Zero;
}
// 数据域波特率
path = String.Format("{0}/canfd_dbit_baud_rate", chn_idx);
ret = ZLGCAN.ZCAN_SetValue(dev, path, "2000000"); // 2M
if (ret != 1)
{
Console.WriteLine("设置数据域波特率失败");
Console.ReadKey();
return IntPtr.Zero;
}
// 终端电阻
path = String.Format("{0}/initenal_resistance", chn_idx);
string resistance = "1";//1-使能 0-禁能
ret = ZLGCAN.ZCAN_SetValue(dev, path, resistance);
if (ret != 1)
{
Console.WriteLine("设置终端电阻失败");
Console.ReadKey();
return IntPtr.Zero;
}
// 初始化通道
ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG InitConfig = new ZLGCAN.ZCAN_CHANNEL_INIT_CONFIG(); // 结构体
InitConfig.can_type = 1; // 1 - CANFD (USBCANFD 只能选择CANFD模式)
InitConfig.config.canfd.mode = 0; // 0 - 正常模式1 - 只听模式
IntPtr P2InitConfig = Marshal.AllocHGlobal(Marshal.SizeOf(InitConfig));
Marshal.StructureToPtr(InitConfig, P2InitConfig, true); // 转成指针
temp = ZLGCAN.ZCAN_InitCAN(dev, (uint)chn_idx, P2InitConfig);
Marshal.FreeHGlobal(P2InitConfig); // 释放内存
if (temp == IntPtr.Zero)
{
Console.WriteLine("初始化通道失败");
Console.ReadKey();
return IntPtr.Zero;
}
Console.WriteLine("初始化通道成功");
//// 设置滤波
//value = "0"; // 0-标准帧 1-扩展帧
// path = String.Format("{0}/filter_mode", chn_idx);
// ret = ZLGCAN.ZCAN_SetValue(deviceHandle, path, value);
// value = "0x731"; // 起始 ID
// path = String.Format("{0}/filter_start", chn_idx);
// ret = ZLGCAN.ZCAN_SetValue(deviceHandle, path, value);
// value = "0x7DF"; // 结束 ID
// path = String.Format("{0}/filter_end", chn_idx);
// ret = ZLGCAN.ZCAN_SetValue(deviceHandle, path, value);
// value = "0"; // 使能滤波
// path = String.Format("{0}/filter_ack", chn_idx);
// ret = ZLGCAN.ZCAN_SetValue(deviceHandle, path, value);
// if (ret != 1)
// {
// Console.WriteLine("使能滤波失败");
// Console.ReadKey();
// return IntPtr.Zero;
// }
if (Program.isBusload == 1) // 通道利用率
{
// 通道利用率上报
path = String.Format("{0}/set_bus_usage_enable", chn_idx);
ret = ZLGCAN.ZCAN_SetValue(dev, path, "1");
// 总线利用率上报周期
path = String.Format("{0}/set_bus_usage_period", chn_idx);
ret = ZLGCAN.ZCAN_SetValue(dev, path, "500");
}
//打开通道
uint Ret = ZLGCAN.ZCAN_StartCAN(temp);
if (Ret != 1)
{
Console.WriteLine("打开通道失败");
Console.ReadKey();
return IntPtr.Zero;
}
Console.WriteLine("打开通道成功");
return temp;
}
// 总线利用率
static void Thread_Busload(object obj)
{
ThreadBusload parameters = (ThreadBusload)obj;
while (Program.isRunning)
{
IntPtr pUse = ZLGCAN.ZCAN_GetValue(Program.devHandle, parameters.chnNum +"/get_bus_usage/1" );
if (pUse != IntPtr.Zero)
{
ZLGCAN.BusUsage busUsage = (ZLGCAN.BusUsage)Marshal.PtrToStructure(pUse, typeof(ZLGCAN.BusUsage));
Console.WriteLine("总线利用率: {0}%{1}帧/秒", busUsage.nBusUsage / 100, busUsage.nFrameCount);
}
Thread.Sleep(500);
}
}
// 接收线程
static void ReceiveThread(object obj)
{
ThreadParams parameters = obj as ThreadParams;
if (parameters == null) return;
// 配置缓冲区大小
const int BUFFER_SIZE = 100;
// 预计算结构体大小
int canStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Receive_Data));
int canfdStructSize = Marshal.SizeOf(typeof(ZLGCAN.ZCAN_ReceiveFD_Data));
// 分配非托管内存(循环外一次分配,复用避免频繁申请释放)
IntPtr pCanBuffer = Marshal.AllocHGlobal(canStructSize * BUFFER_SIZE); // CAN接收缓冲区
IntPtr pCanfdBuffer = Marshal.AllocHGlobal(canfdStructSize * BUFFER_SIZE); // CANFD接收缓冲区
try
{
while (Program.isRunning)
{
uint recNum = ZLGCAN.ZCAN_GetReceiveNum(parameters.chn, 0); // 0-CAN
if (recNum > 0)
{
// 限制接收数量不超过缓冲区大小(双重保险)
uint actualReceive = ZLGCAN.ZCAN_Receive(
parameters.chn,
pCanBuffer, // 传入非托管缓冲区指针C风格
BUFFER_SIZE,
10
);
if (actualReceive > 0)
{
for (int i = 0; i < actualReceive; i++)
{
// 计算当前CAN数据的指针
IntPtr pCurrentCan = (IntPtr)(pCanBuffer.ToInt64() + i * canStructSize);
// 非托管内存转换为C#结构体
ZLGCAN.ZCAN_Receive_Data recData = (ZLGCAN.ZCAN_Receive_Data)Marshal.PtrToStructure(
pCurrentCan,
typeof(ZLGCAN.ZCAN_Receive_Data)
);
// 可选ID过滤
// uint canId = recData.frame.can_id & 0x1FFFFFFF;
// if (canId != 0x731 && canId != 0x7B1)
// continue;
// 通道号和时间戳
Console.Write(String.Format("CHN:{0} [{1}] CAN ", parameters.chnNum, recData.timestamp));
// 帧类型(标准/扩展)
Console.Write((recData.frame.can_id & (1 << 31)) != 0 ? "扩展帧 " : "标准帧 ");
// 方向Tx/Rx
Console.Write((recData.frame.__pad & 0x20) == 0x20 ? "Tx " : "Rx ");
// 数据长度校验
int dataLen = Math.Min(recData.frame.can_dlc, recData.frame.data != null ? recData.frame.data.Length : 0);
// ID和数据保留原格式
Console.WriteLine(String.Format("ID:{0:X} {1}",
recData.frame.can_id & 0x1FFFFFFF,
Program.b_array2string(recData.frame.data, dataLen)
));
}
}
}
recNum = ZLGCAN.ZCAN_GetReceiveNum(parameters.chn, 1); // 1-CANFD
if (recNum > 0)
{
uint actualReceive = ZLGCAN.ZCAN_ReceiveFD(
parameters.chn,
pCanfdBuffer, // CANFD非托管缓冲区指针
BUFFER_SIZE,
10
);
if (actualReceive > 0)
{
for (int i = 0; i < actualReceive; i++)
{
// 计算当前CANFD数据的指针
IntPtr pCurrentCanfd = (IntPtr)(pCanfdBuffer.ToInt64() + i * canfdStructSize);
// 非托管内存转换为C#结构体
ZLGCAN.ZCAN_ReceiveFD_Data recFDData = (ZLGCAN.ZCAN_ReceiveFD_Data)Marshal.PtrToStructure(
pCurrentCanfd,
typeof(ZLGCAN.ZCAN_ReceiveFD_Data)
);
// 通道号和时间戳
Console.Write(String.Format("CHN:{0} [{1}] CANFD", parameters.chnNum, recFDData.timestamp));
// CANFD加速标志
if ((recFDData.frame.flags & 0x01) == 0x01)
Console.Write("加速");
// 帧类型(标准/扩展)
Console.Write((recFDData.frame.can_id & (1 << 31)) != 0 ? " 扩展帧" : " 标准帧");
// 方向Tx/Rx
Console.Write((recFDData.frame.flags & 0x20) == 0x20 ? " Tx " : " Rx ");
// 数据长度校验
int dataLen = Math.Min(recFDData.frame.len, recFDData.frame.data != null ? recFDData.frame.data.Length : 0);
// ID和数据
Console.WriteLine(String.Format("ID:{0:X} {1}",
recFDData.frame.can_id & 0x1FFFFFFF,
Program.b_array2string(recFDData.frame.data, dataLen)
));
}
}
}
ZLGAPI.ZLGCAN.ZCAN_CHANNEL_ERR_INFO errData = new ZLGAPI.ZLGCAN.ZCAN_CHANNEL_ERR_INFO{
error_code = 0,
passive_ErrData = new byte[3],
arLost_ErrData = 0
};
uint ret = ZLGAPI.ZLGCAN.ZCAN_ReadChannelErrInfo(parameters.chn, ref errData);
Analyze_err_frame(errData);
Thread.Sleep(10);
}
}
finally
{
// 确保非托管内存必释放
Marshal.FreeHGlobal(pCanfdBuffer);
Marshal.FreeHGlobal(pCanBuffer);
}
}
// 接收线程(合并接收)
static void ReceiveThread_Merge(object obj)
{
ThreadParams parameters = (ThreadParams)obj;
// 预计算结构体大小
int dataObjSize = Marshal.SizeOf(typeof(ZLGCAN.ZCANDataObj));
int canfdSize = Marshal.SizeOf(typeof(ZLGCAN.ZCANCANFDData));
int linSize = Marshal.SizeOf(typeof(ZLGCAN.ZCANLINData));
// 分配非托管内存(循环外分配,复用避免频繁申请释放)
IntPtr pDataObjs = Marshal.AllocHGlobal(dataObjSize * 100); // 写死100个最大可以面对绝大部分情况如果是动态长度容易崩溃
IntPtr pCanfdBuffer = Marshal.AllocHGlobal(canfdSize);
IntPtr pLinBuffer = Marshal.AllocHGlobal(linSize);
try
{
while (Program.isRunning)
{
// 获取待接收数据量,无数据则休眠后继续
uint recvNum = ZLGCAN.ZCAN_GetReceiveNum(parameters.dev, 2);
if (recvNum == 0)
{
Thread.Sleep(10);
continue;
}
// 接收数据
uint actualRecv = ZLGCAN.ZCAN_ReceiveData(parameters.dev, pDataObjs, 100, 10);
if (actualRecv == 0)
{
continue;
}
// 遍历处理每个接收的数据
for (int i = 0; i < actualRecv; i++)
{
// 计算当前数据对象指针
IntPtr pCurrentData = (IntPtr)(pDataObjs.ToInt64() + i * dataObjSize);
ZLGCAN.ZCANDataObj dataObj = (ZLGCAN.ZCANDataObj)Marshal.PtrToStructure(pCurrentData, typeof(ZLGCAN.ZCANDataObj));
// 根据数据类型处理
switch (dataObj.dataType)
{
case 1: // CAN/CANFD
// 复制data到非托管缓冲区
Marshal.Copy(dataObj.data, 0, pCanfdBuffer, canfdSize);
ZLGCAN.ZCANCANFDData canfdData = (ZLGCAN.ZCANCANFDData)Marshal.PtrToStructure(pCanfdBuffer, typeof(ZLGCAN.ZCANCANFDData));
//// 可选ID过滤调试用
//uint canId = canfdData.frame.can_id & 0x1FFFFFFF;
//if (canId != 0x731 && canId != 0x7B1)
// continue;
// 通道和时间戳
Console.Write(String.Format("CHN:{0} [{1}] ", dataObj.chnl, canfdData.timeStamp));
// CAN/CANFD类型 + 加速标志
if ((canfdData.flag & 1) == 0)
Console.Write("CAN");
else
{
Console.Write("CANFD");
if ((canfdData.frame.flags & 1) == 1)
Console.Write("加速");
}
// 帧类型(标准/扩展)
Console.Write((canfdData.frame.can_id & (1 << 31)) != 0 ? " 扩展帧 " : " 标准帧 ");
// 方向TX/RX
Console.Write((canfdData.flag & (1 << 9)) != 0 ? "TX " : "RX ");
// ID
Console.Write(String.Format("ID:{0:X} ", canfdData.frame.can_id & 0x1FFFFFFF));
// 打印数据
int canDataLen = Math.Min(canfdData.frame.len, canfdData.frame.data != null ? canfdData.frame.data.Length : 0);
for (int j = 0; j < canDataLen; j++)
Console.Write(String.Format("{0:X2} ", canfdData.frame.data[j]));
Console.WriteLine();
break;
case 4: // LIN
// 校验数据长度足够,避免越界复制
if (dataObj.data.Length < linSize)
break;
// 直接复制data到非托管缓冲区
Marshal.Copy(dataObj.data, 0, pLinBuffer, linSize);
ZLGCAN.ZCANLINData linData = (ZLGCAN.ZCANLINData)Marshal.PtrToStructure(pLinBuffer, typeof(ZLGCAN.ZCANLINData));
// 拼接打印信息
Console.Write(String.Format("CHN:{0} [{1}] PID:{2:X} ",
dataObj.chnl, linData.rxData.timeStamp, linData.pid.rawVal & 0x3F));
// 方向TX/RX
Console.Write(linData.rxData.dir == 1 ? "TX Data: " : "RX Data: ");
// 打印数据
int linDataLen = Math.Min(linData.rxData.datalen, linData.rxData.data != null ? linData.rxData.data.Length : 0);
for (int j = 0; j < linDataLen; j++)
Console.Write(String.Format("{0:X2} ", linData.rxData.data[j]));
Console.WriteLine();
break;
case 6: // LIN错误
Console.WriteLine("LIN 错误");
break;
}
}
}
}
finally
{
// 确保非托管内存必释放(避免泄漏)
Marshal.FreeHGlobal(pLinBuffer);
Marshal.FreeHGlobal(pCanfdBuffer);
Marshal.FreeHGlobal(pDataObjs);
}
}
// 发送示例
static void Send_test(IntPtr chnHandle)
{
// CAN
ZLGCAN.ZCAN_Transmit_Data[] can_data = new ZLGCAN.ZCAN_Transmit_Data[10];
IntPtr p2CanData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Transmit_Data)) * 10);
byte[] sourceData = new byte[] { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 ,0x77,0x88};
for (int i = 0; i < 10; i++)
{
can_data[i].frame = new ZLGCAN.can_frame();
can_data[i].transmit_type = 0; // 0-正常发送
can_data[i].frame.can_id = MakeCanId((uint)0x10, 0, 0, 0); // ID
can_data[i].frame.can_dlc = 8; // 数据长度
can_data[i].frame.__pad |= 0x20; // 发送回显
can_data[i].frame.data = new byte[8]; // 此成员必须new8个字节否则无法运行数据长度修改frame.can_dlc成员即可
Array.Copy(sourceData, can_data[i].frame.data, Math.Min(8, sourceData.Length));
Marshal.StructureToPtr(can_data[i], (IntPtr)(p2CanData + i * Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Transmit_Data))), true);
}
uint ret = ZLGCAN.ZCAN_Transmit(chnHandle, p2CanData, 10);
Console.WriteLine("发送 {0} 帧CAN报文", ret);
// CANFD
ZLGCAN.ZCAN_TransmitFD_Data[] canfd_data = new ZLGCAN.ZCAN_TransmitFD_Data[10];
IntPtr p2CanFDData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.ZCAN_TransmitFD_Data)) * 10);
for (int i = 0; i < 10; i++)
{
canfd_data[i].frame = new ZLGCAN.canfd_frame();
canfd_data[i].transmit_type = 0; // 0-正常发送
canfd_data[i].frame.can_id = MakeCanId((uint)0x10, 0, 0, 0); // ID
canfd_data[i].frame.len = 64; // 数据长度
canfd_data[i].frame.flags |= 0x20; // 发送回显
canfd_data[i].frame.data = new byte[64]; // 此成员必须new64个字节否则无法运行数据长度修改frame.len成员即可
for (int j = 0; j < canfd_data[i].frame.len; j++)
canfd_data[i].frame.data[j] = (byte)j;
Marshal.StructureToPtr(canfd_data[i], (IntPtr)(p2CanFDData + i * Marshal.SizeOf(typeof(ZLGCAN.ZCAN_TransmitFD_Data))), true);
}
uint retfd = ZLGCAN.ZCAN_TransmitFD(chnHandle, p2CanFDData, 10);
Console.WriteLine("发送 {0} 帧CANFD报文", retfd);
Marshal.FreeHGlobal(p2CanData);
Marshal.FreeHGlobal(p2CanFDData);
}
// 合并发送
static void Send_test_Merge(IntPtr dev)
{
const int FRAME_COUNT = 10; // 发送帧数量
ZLGCAN.ZCANDataObj[] dataObjs = new ZLGCAN.ZCANDataObj[FRAME_COUNT];
IntPtr pDataObjs = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.ZCANDataObj)) * FRAME_COUNT);
// canfd_frame
var baseFrame = new ZLGCAN.canfd_frame
{
can_id = MakeCanId(0x10, 0, 0, 0), // ID
len = 64, // 长度
flags = 0x01, // 1-CANFD加速
data = new byte[64]
};
for (int i = 0; i < baseFrame.data.Length; i++)
{
baseFrame.data[i] = (byte)(i % 0xFF); // 数据
}
for (int i = 0; i < FRAME_COUNT; i++)
{
// ZCANCANFDData
var canfdData = new ZLGCAN.ZCANCANFDData
{
timeStamp = 0, // 普通发送时 timeStamp 字段无意义
flag = 0,
extraData = new byte[4],
frame = baseFrame,
frameType = 1, // 0-CAN 1-CANFD
txEchoRequest = 1 // 1-发送回显
};
// ZCANDataObj
dataObjs[i] = new ZLGCAN.ZCANDataObj
{
dataType = 1, // 1-CAN/CANFD
chnl = 0, // 通道
flag = 0, // 未使用
data = new byte[92]
};
// 结构体转字节数组
IntPtr pCanFd = Marshal.AllocHGlobal(Marshal.SizeOf(canfdData));
try
{
Marshal.StructureToPtr(canfdData, pCanFd, false);
Marshal.Copy(pCanFd, dataObjs[i].data, 0, Marshal.SizeOf(canfdData));
}
finally
{
Marshal.FreeHGlobal(pCanFd);
}
// 拷贝到非托管内存
Marshal.StructureToPtr(dataObjs[i], IntPtr.Add(pDataObjs, i * Marshal.SizeOf(typeof(ZLGCAN.ZCANDataObj))), true);
}
// 发送
uint sentCount = ZLGCAN.ZCAN_TransmitData(dev, pDataObjs, (uint)FRAME_COUNT);
Console.WriteLine("合并发送 {0} 帧报文", sentCount);
Marshal.FreeHGlobal(pDataObjs);
}
// 定时发送
static void AutoSend_test(IntPtr dev, int chn_idx)
{
// 第一条定时任务CAN
ZLGCAN.ZCAN_Transmit_Data can_data = new ZLGCAN.ZCAN_Transmit_Data();
can_data.frame = new ZLGCAN.can_frame();
can_data.transmit_type = 0; // 0-正常发送
can_data.frame.can_id = MakeCanId((uint)0x10, 0, 0, 0); // ID
can_data.frame.can_dlc = 8; // 数据长度
can_data.frame.__pad |= 0x20; // 发送回显
can_data.frame.data = new byte[8];
for (int j = 0; j < can_data.frame.can_dlc; j++)
can_data.frame.data[j] = (byte)j;
ZLGCAN.ZCAN_AUTO_TRANSMIT_OBJ auto_can = new ZLGCAN.ZCAN_AUTO_TRANSMIT_OBJ
{
index = 0,
enable = 1,
interval = 500,
obj = new ZLGCAN.ZCAN_Transmit_Data()
};
auto_can.obj = can_data;
string path = String.Format("{0}/auto_send", chn_idx);
uint ret = ZLGCAN.ZCAN_SetValue(dev, path, ref auto_can); // 设置
if (ret != 1)
{
Console.WriteLine("设置定时发送失败");
}
//// 第二条定时任务CANFD
//ZLGCAN.ZCAN_TransmitFD_Data canfd_data = new ZLGCAN.ZCAN_TransmitFD_Data();
//canfd_data.frame = new ZLGCAN.canfd_frame();
//canfd_data.transmit_type = 0; // 0-正常发送
//canfd_data.frame.can_id = MakeCanId((uint)0x20, 0, 0, 0); // ID
//canfd_data.frame.len = 64; // 数据长度
//canfd_data.frame.flags |= 0x20; // 发送回显
//canfd_data.frame.data = new byte[64];
//for (int j = 0; j < canfd_data.frame.len; j++)
// canfd_data.frame.data[j] = (byte)j;
//ZLGCAN.ZCANFD_AUTO_TRANSMIT_OBJ auto_canfd = new ZLGCAN.ZCANFD_AUTO_TRANSMIT_OBJ
//{
// index = 1,
// enable = 1,
// interval = 500,
// obj = new ZLGCAN.ZCAN_TransmitFD_Data()
//};
//auto_canfd.obj = canfd_data;
//path = String.Format("{0}/auto_send_canfd", chn_idx);
//ret = ZLGCAN.ZCAN_SetValue(dev, path, ref auto_canfd); // 设置
//if (ret != 1)
//{
// Console.WriteLine("设置定时发送失败");
//}
// 启动通道定时任务
path = String.Format("{0}/apply_auto_send", chn_idx);
ret = ZLGCAN.ZCAN_SetValue(dev, path, "0");
if (ret != 1)
{
Console.WriteLine("启动定时发送失败");
}
// 覆盖第一条定时任务
Thread.Sleep(2000);
can_data.frame.can_id = MakeCanId((uint)0x222, 0, 0, 0); // 新ID
auto_can.obj = can_data;
path = String.Format("{0}/auto_send", chn_idx);
ret = ZLGCAN.ZCAN_SetValue(dev, path, ref auto_can);
if (ret != 1)
{
Console.WriteLine("第二次设置定时发送失败");
}
else
{
Console.WriteLine("覆盖定时发送成功");
}
Thread.Sleep(5000);
// 清空定时发送
path = String.Format("{0}/clear_auto_send", 0);
ret = ZLGCAN.ZCAN_SetValue(dev, path, "0");
if (ret != 1)
{
Console.WriteLine("清空定时发送失败");
}
}
// 设置队列发送and队列发送示例
static void QueueSend_test(IntPtr dev, IntPtr chnHandle)
{
//// 使能队列发送
//uint ret = ZLGCAN.ZCAN_SetValue(Program.dev, "0/set_send_mode", "1"); // USBCANFD V1.01版本的才需要配置
//if (ret != 1)
//{
// Console.WriteLine("设置队列发送失败");
// Console.ReadKey();
//}
uint ret = 0;
const int FRAME_COUNT = 100; // 发送帧数量
// CAN
ZLGCAN.ZCAN_Transmit_Data[] can_data = new ZLGCAN.ZCAN_Transmit_Data[100];
IntPtr p2CanData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Transmit_Data)) * FRAME_COUNT);
for (int i = 0; i < FRAME_COUNT; i++)
{
can_data[i].frame = new ZLGCAN.can_frame();
can_data[i].transmit_type = 0; // 0-正常发送
can_data[i].frame.can_id = MakeCanId((uint)0x10, 0, 0, 0); // ID
can_data[i].frame.can_dlc = 8; // 数据长度
can_data[i].frame.__pad |= 0x20; // 发送回显
can_data[i].frame.__pad |= 0x80; // 设置队列发送 0x80单位ms,0xC0单位100us
can_data[i].frame.__res0 = 0xA; // res1 和 res0两个字节存储时间间隔
can_data[i].frame.__res1 = 0x0;
can_data[i].frame.data = new byte[8];
for (int j = 0; j < can_data[i].frame.can_dlc; j++)
can_data[i].frame.data[j] = (byte)j;
Marshal.StructureToPtr(can_data[i], (IntPtr)(p2CanData + i * Marshal.SizeOf(typeof(ZLGCAN.ZCAN_Transmit_Data))), true);
}
// 获取队列发送缓存
IntPtr free_count_p = ZLGCAN.ZCAN_GetValue(dev, "0/get_device_available_tx_count/1");
int free_count = Marshal.ReadInt32(free_count_p);
Console.WriteLine("队列缓存剩余:{0} 帧", free_count);
if (free_count > FRAME_COUNT)
{
ret = ZLGCAN.ZCAN_Transmit(chnHandle, p2CanData, 100); // 下发给设备
Console.WriteLine("发送 {0} 帧CAN报文", ret);
}
// CANFD
ZLGCAN.ZCAN_TransmitFD_Data[] canfd_data = new ZLGCAN.ZCAN_TransmitFD_Data[FRAME_COUNT];
IntPtr p2CanFDData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ZLGCAN.ZCAN_TransmitFD_Data)) * FRAME_COUNT);
for (int i = 0; i < FRAME_COUNT; i++)
{
canfd_data[i].frame = new ZLGCAN.canfd_frame();
canfd_data[i].transmit_type = 0; // 0-正常发送
canfd_data[i].frame.can_id = MakeCanId((uint)0x11, 0, 0, 0); // ID
canfd_data[i].frame.len = 64; // 数据长度
canfd_data[i].frame.flags |= 0x20; // 发送回显
canfd_data[i].frame.flags |= 0x80; // 设置队列发送 0x80单位ms,0xC0单位100us
canfd_data[i].frame.__res0 = 0x14;
canfd_data[i].frame.__res1 = 0x0;
canfd_data[i].frame.data = new byte[64];
for (int j = 0; j < canfd_data[i].frame.len; j++)
canfd_data[i].frame.data[j] = (byte)j;
Marshal.StructureToPtr(canfd_data[i], (IntPtr)(p2CanFDData + i * Marshal.SizeOf(typeof(ZLGCAN.ZCAN_TransmitFD_Data))), true);
}
// 获取队列发送缓存
free_count_p = ZLGCAN.ZCAN_GetValue(dev, "0/get_device_available_tx_count/1");
free_count = Marshal.ReadInt32(free_count_p);
Console.WriteLine("队列缓存剩余:{0} 帧", free_count);
if (free_count > FRAME_COUNT)
{
uint retfd = ZLGCAN.ZCAN_TransmitFD(chnHandle, p2CanFDData, 10);
Console.WriteLine("发送 {0} 帧CANFD报文", retfd);
}
Thread.Sleep(2000); // 发送2秒后清空
ret = ZLGCAN.ZCAN_SetValue(dev, "0/clear_delay_send_queue", "0"); // 清空队列缓存
if (ret != 1)
{
Console.WriteLine("清空队列缓存失败");
}
Marshal.FreeHGlobal(p2CanData);
Marshal.FreeHGlobal(p2CanFDData);
}
// 错误帧解析
static void Analyze_err_frame(ZLGAPI.ZLGCAN.ZCAN_CHANNEL_ERR_INFO errData)
{
// 错误码非0时先输出错误码替换$为string.Format
if (errData.error_code != 0x0)
{
Console.Write(string.Format("错误码:{0}", errData.error_code));
}
// 匹配错误码逻辑与C++ switch完全一致
switch (errData.error_code)
{
case 0x1:
Console.WriteLine("CAN 控制器内部FIFO溢出");
break;
case 0x2:
Console.WriteLine("CAN 控制器错误报警");
break;
case 0x4:
Console.Write("CAN 控制器消极错误,");
// 替换$为string.Format多个参数按顺序占位
Console.WriteLine(string.Format("Rx ErrNUM: {0}Tx ErrNUM: {1}",
errData.passive_ErrData[1], errData.passive_ErrData[2]));
break;
case 0x8:
Console.WriteLine("CAN 控制器仲裁丢失");
break;
case 0x10:
Console.WriteLine("CAN 控制器总线错误");
break;
case 0x20:
Console.WriteLine("CAN 控制器总线关闭");
break;
case 0x40:
Console.WriteLine("CAN 缓存溢出");
break;
default:
break;
}
}
// 自定义序列号
static void SN_test(IntPtr dev)
{
// 设置自定义sn号
if (0 == ZLGCAN.ZCAN_SetValue(dev, "0/set_cn", "C#01"))
{
Console.WriteLine("设置自定义序列号失败");
Console.ReadKey();
}
// 获取自定义sn号
IntPtr P2CN = ZLGCAN.ZCAN_GetValue(dev, "0/get_cn/1");
string CN = Marshal.PtrToStringAnsi(P2CN);
Console.WriteLine("设备的自定义序列号:{0}", CN);
}
// 构造CANID
static uint MakeCanId(uint id, int eff, int rtr, int err)
{
uint ueff = (uint)(!!(Convert.ToBoolean(eff)) ? 1 : 0); // 0 标准帧1 扩展帧
uint urtr = (uint)(!!(Convert.ToBoolean(rtr)) ? 1 : 0); // 0 数据帧1 远程帧
uint uerr = (uint)(!!(Convert.ToBoolean(err)) ? 1 : 0); // 0 CAN帧1 错误帧(目前只能设置为 0
return id | ueff << 31 | urtr << 30 | uerr << 29;
}
// byte[] -> string
public static string b_array2string(byte[] ByteIn, int LEN)
{
// 空值校验(避免空引用/越界异常)
if (ByteIn == null || ByteIn.Length == 0 || LEN == 0)
return "";
// 限制实际处理长度
int actualLen = Math.Min(LEN, ByteIn.Length);
// 用StringBuilder优化拼接
System.Text.StringBuilder sb = new System.Text.StringBuilder(actualLen * 3); // 预分配容量每个字节占3字符XX + 空格)
for (int i = 0; i < actualLen; i++)
{
sb.Append(ByteIn[i].ToString("X2")); // 大写十六进制
if (i < actualLen - 1) // 最后一个字节后不添加空格
sb.Append(" ");
}
return sb.ToString();
}
}
}