设备:ZCAN_USBCANFD_200U
引入ZLGCAN库
打开官方资源界面:https://www.zlg.cn/can/down/down/id/22.html
打开 应用软件 -【应用软件】ZLGCAN上位机例程
下载其中的 zlgcan二次开发接口函数库(2024.4.16).zip
将文件中的x64/x86的lib根据需求添加到环境变量或者直接在项目中引用。
此处举例两种引用方式(Visual Studio & CMake)
- VS需在属性-链接器-输入-添加依赖项中添加 zlgcan_x64(x86)/zlgcan.lib
- CMake需在CMakeLists中添加如下内容:
target_link_libraries(${PROJECT_NAME} PRIVATE
debug $ENV{ZLGCAN_PATH}/zlgcan_x64/zlgcan.lib
optimized $ENV{ZLGCAN_PATH}/zlgcan_x64/zlgcan.lib
)
此处ZLGCAN_PATH已经在环境变量中添加,路径为 D:\xx\CAN_lib\zlgcanlib(路径根据使用者的实际使用路径变化。)
代码生成时会根据选择的Debug/Release生成对应的文件夹,将ZLGCAN开发接口函数库中的kerneldlls文件夹及zlgcan.dll文件拷贝到Debug/Release文件夹中。以确保ZLGCAN的接口函数可以正常使用。
此时环境正确配置完成。
开启ZLGCAN设备
创建设备句柄
DEVICE_HANDLE mDeviceHandle;
调用ZCAN_OpenDevice函数。ZCAN_OpenDevice的三个参数分别是,设备类型,设备索引,和一个保留参数位。
我使用的设备是USB 200U 因此在设备类型中填写ZCAN_USBCANFD_200U,其他两个位置填入0。
mDeviceHandle = ZCAN_OpenDevice(ZCAN_USBCANFD_200U, 0, 0);
启动ZLGCAN设备通道
创建通道句柄
CHANNEL_HANDLE mChannelHandle[2];
初始化通道配置,设置CAN协议,设置终端电阻,设置波特率。
ZCAN_CHANNEL_INIT_CONFIG config;
// 设置CAN协议
char path[50] = { 0 };
char value[100] = { 0 };
sprintf_s(path, "%d/canfd_standard", channel);
sprintf_s(value, "%d", 0);
unsigned int ret = ZCAN_SetValue(deviceHandle, path, value);
if (!ret)
{
return false;
}
// 设置终端电阻
char path3[50] = { 0 };
sprintf_s(path3, "%d/initenal_resistance", channel);
char value3[10] = { 0 };
sprintf_s(value3, "%d", 1);
ret = ZCAN_SetValue(deviceHandle, path3, value3);
if (!ret)
{
return false;
}
// 设置波特率
char path2[50] = { 0 };
sprintf_s(path2, "%d/canfd_abit_baud_rate", 1);
char value2[10] = { 0 };
sprintf_s(value2, "%d", 500000);
int ret1 = ZCAN_SetValue(deviceHandle, path2, value2);
sprintf_s(path2, "%d/canfd_dbit_baud_rate", 1);
sprintf_s(value2, "%d", 2000000);
int ret2 = ZCAN_SetValue(deviceHandle, path2, value2);
if ((ret1 && ret2))
{
config.can_type = TYPE_CANFD;
config.canfd.mode = 0;
config.canfd.filter = 1;
config.canfd.acc_code = 0x00000000;
config.canfd.acc_mask = 0xFFFFFFFF;
channelHandle = ZCAN_InitCAN(deviceHandle, channel, &config);
if (INVALID_CHANNEL_HANDLE == channelHandle)
{
printf("初始化CAN失败");
return false;
}
}
else
{
printf("设置波特率失败");
return false;
}
if (ZCAN_StartCAN(channelHandle) != STATUS_OK)
{
printf("启动通道失败");
return false;
}
此流程如果可以正常完成,观察ZLGCAN的指示灯状态,如果由暗转亮,证明已经正常开启了通道,可以继续之后的操作。如果没有,回去看下kerneldlls文件是否正确置入Debug/Release文件夹,或者设备的连接是否正常。
唤醒设备及解析设备信息
此处需根据项目需求进行处理。如果项目中作为数据源的设备需要唤醒,可以先创建线程,并持续发送唤醒命令。唤醒命令的发送如下。线程的处理根据个人需求来做,在此不做举例。
unsigned char data[8] = { 0x00 ,0x01 ,0x02 ,0x03 ,0x04 ,0x05 ,0x06 ,0x07 };
//向两个通道持续发送命令,保持通信
for (int i = 0; i < 2; i++)
{
if (mChannelHandle[i] != INVALID_CHANNEL_HANDLE)
{
mZlgCan.sendCANFDMsg(0x600, data, 8, mChannelHandle[i]);
}
}
//此处使用的sendCANFDMsg如下
bool ZlgCan::sendCANFDMsg(unsigned int canId, unsigned char* data, unsigned char dataLen, CHANNEL_HANDLE channelHandle)
{
UINT result = 0;
ZCAN_TransmitFD_Data canfd_data;
bool bDelay = 0;
UINT nDelayTime = 1000;
memset(&canfd_data, 0, sizeof(canfd_data));
canfd_data.frame.can_id = canId;
canfd_data.frame.len = dataLen;
memcpy(canfd_data.frame.data, data, dataLen);
canfd_data.transmit_type = 0;
canfd_data.frame.flags |= 1 ? CANFD_BRS : 0;
if (bDelay)
{
canfd_data.frame.flags |= TX_DELAY_SEND_FLAG;
canfd_data.frame.__res0 = 0x00FF & nDelayTime;
canfd_data.frame.__res1 = 0xFF00 & nDelayTime > 8;
}
//由于有时会遇到channel只连接了一路的情况,外围的循环获取数据会导致数据堵塞,
//为避免此问题,解决办法就是直接在初始化时,如果无法正常调用,就直接关闭没有设备连接的channel
//此处的超时处理就是为了解决这个问题
time_t stime, curtime;
time(&stime);
result = ZCAN_TransmitFD(channelHandle, &canfd_data, 1);
time(&curtime);
int passtime = (curtime - stime) * 1000;
if (passtime > 1000)
{
ZCAN_ResetCAN(channelHandle);
}
if (result != 1)
{
return false;
}
else
{
return true;
}
}
假设目前设备已经在持续发送数据,那么在连接通道后,我们需要做的就是解析接收到的设备信息。解析过程中的实际解析内容要根据实际项目去适配,在此只举例解析的流程,不关心解析内容。
开启一个接收数据并解析数据的线程,先接收,后解析。
接收过程如下:
ZCAN_ReceiveFD_Data canfd_data[100];
if (len = ZCAN_GetReceiveNum(channelHandle, TYPE_CANFD))
{
len = ZCAN_ReceiveFD(channelHandle, canfd_data, 100, 66);
for (auto i = 0; i < len; ++i)
{
memcpy(&canfdArr[i], &canfd_data[i].frame, sizeof(canfd_frame));
}
}
解析则根据canfd_frame中的can_id来解析对应的数据。
can_id的值为多少到多少是哪段数据,需要根据协议进行实际的判断。
canfd_frame canfdframe[2][100];
//举例,例如id为0x400
if (canfdframe[chn][i].can_id == 0x400)
{
//根据其中的数据位解析对应的数据
}
此处除了手动去解析数据位,还有种方法是直接根据dbc去解析,此时可以使用ZLGCAN官方的zdbc_lib库来做解析。
ZDBC_LIB的使用
打开文章上方的ZLG官网,找到并下载 应用软件 - 【应用软件】DBC解析模块库和示例代码_240612
在下载的文件中找到zdbc_lib,使用跟上面相同的方式引入lib。
首先需要加载dbc文件:
DBCHandle mDBC = ZDBC_Init();
if (INVALID_DBC_HANDLE == mDBC)
{
qDebug() << "[DBC] Init dbc handle failed!";
return false;
}
FileInfo fileinfo;
strcpy_s(fileinfo.strFilePath, _MAX_FILE_PATH_, path.toStdString().c_str());
fileinfo.merge = false;
if (!ZDBC_LoadFile(mDBC, &fileinfo))
{
qDebug() << "[DBC] Load dbc file failed!";
return false;
}
if (0 == ZDBC_GetMessageCount(mDBC))
{
qDebug() << "[DBC] File no message!";
return false;
}
成功加载dbc文件后,便可在接收数据的处理后进行解析:
if (!ZDBC_Analyse(mDBC, &canfdFrame, FT_CANFD, pMsg))
{
return false;
}
double mData[MAX_MESSAGE_SIGNAL_NUM];
for (auto i = 0; i < pMsg->nSignalCount; ++i)
{
double ret = ZDBC_CalcActualValue(&pMsg->vSignals[i], &pMsg->vSignals[i].nRawValue);
mData[i] = ret;
}
parseData(mData, pMsg->nSignalCount, data);
此处使用ZDBC库解析后只需要读取信号中的值,而不需要在代码中再去做协议解析。
以上只是个人在开发中的一些收获和认识,肯定会有很多不足的地方,欢迎各位大佬指点问题,也欢迎有相关开发经验或者跟我有类似问题的朋友多多交流。