Labwindows/CVI 编写CAN通讯的上位机
Labwindows/CVI 编写CAN通讯的上位机
前言
本人从事电机测试已久,会编写DSP的CAN通讯程序编写,但是一直对上位机的编译学不会。学习了很长一段时间的C#但是还是没有弄明白动态链接库和委托,最后我公司来了一位软件经理指导我一下,劝我放弃C#转学习Labwindows/CVI这样一来既可以学习C语言又可以学习上位机。通过对比我发现CVI确实比C#更容易一些,接下来我就将我写的过程分享一下。
一、Labwindows如何学习
CVI学习起来很简单,如果有C语言的基础完全没有问题,个人建议学习的话在网上找一个视频,学习一下基本控件,然后学习一下动态链接库和多线程或者异步定时器就可以编写CAN通讯的上位机。CAN通讯的示例我是参照周立功的USBCAN_E_2E_U的【应用程序】labwindows_example(U系列)。
二、编译步骤
1.引入动态库
找到了ControlCAN.dll、ControlCAN.h、ControlCAN.lib三个文件,放到了工程文件夹内,lib文件添加顺序是右击工程->Add Existing File->选中ControlCAN.lib文件。H文件添加顺序是右击工程->Add Existing File->选中ControlCAN.h文件。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201025165932154.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDMzOTcy,size_16,color_FFFFFF,t_70#pic_center)!
注意在生成C文件后,将#include “ControlCAN.h” 添加上
2.如何编译控件
如何编写控件的最重要的一步就是要找到周立功的《接口函数库(二次开发库)》,仔细看看这几个函数VCI_OpenDevice、VCI_StartCAN、VCI_CloseDevice、 VCI_ResetCAN。
首先界面上制作出来这几个控件:打开设备、启动CAN、复位CAN和关闭设备,并右击控件建立Callback函数。
以打开设备为例,编写代码如下:
int CVICALLBACK OpenDevice_callback (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
VCI_INIT_CONFIG config;
DWORD AccCode;
DWORD AccMask;
UCHAR Filter;
UCHAR Mode;
switch (event)
{
case EVENT_COMMIT:
if(connect==1)//如果连接则退出
{
return 0;
}
GetCtrlVal (panelHandle,PANEL_DEVICE_TYPE, &DeviceType);
if(VCI_OpenDevice(DeviceType,0,0)==1) //打开设备
{
SetCtrlVal (panelHandle, PANEL_LED, 1);
}
else
{
SetCtrlVal (panelHandle, PANEL_LED, 0);
MessagePopup("提示","打开设备失败!");
}
//初始化CAN0通道参数
SetBaud (panelHandle, 0, EVENT_COMMIT,0, 0, 0);
AccCode=0; //验收码
AccMask=-1; //屏蔽码
Filter=0; //滤波方式
Mode=0; //模式
config.AccCode=AccCode;
config.AccMask=AccMask;
config.Filter=Filter;
config.Mode=Mode;
config.Timing0=Timing0;
config.Timing1=Timing1;
GetCtrlVal (panelHandle, PANEL_CAN_CAHENNEL, &CANChannel);
if((VCI_InitCAN(DeviceType,0,CANChannel,&config))==0) //初始化
{
MessagePopup("错误","初始化失败");
}
connect=1;
break;
}
return 0;
}
其中启动CAN、复位CAN和关闭设备的代码完全可以参照周立功的示例。
3.如何建立多线程并接受和发送
在启动CAN的程序下建立多线程 CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE, ThreadFunction,NULL, &threadID);
多线程的程序如下
int CVICALLBACK ThreadFunction(void *functionData)
{
int i=0;
unsigned int recLen = 0;
int ReceiveNum=0;
VCI_ERR_INFO errinfo;//错误信信息
static int led_count=0;
while(startflag)
{
GetCtrlVal (panelHandle, PANEL_DEVICE_TYPE, &DeviceType);
GetCtrlVal (panelHandle, PANEL_CAN_CAHENNEL, &CANChannel);
ReceiveNum=VCI_GetReceiveNum(DeviceType,0,CANChannel);//接收到但尚未被读取的帧数量
if(0==ReceiveNum)
{
//注意:如果没有读到数据则必须调用此函数来读取出当前的错误码,
//千万不能省略这一步(即使你可能不想知道错误码是什么)
CANChannel=0;
VCI_ReadErrInfo(DeviceType,0,CANChannel,&errinfo);
}
else
{
SetCtrlVal (panelHandle, PANEL_RECEIVE_BUFFER, ReceiveNum);
}
recLen = VCI_Receive(DeviceType,0,CANChannel,rec,50,400); //接收函数。此函数从指定的设备CAN通道的接收缓冲区中读取数据。
//(设备类型,设备索引,can通道,用来接收的帧结构体VCI_CAN_OBJ数组的首指针用来接收的帧结构体数组的长度,保留参数)
if((recLen>0) &&(recLen!=0xFFFFFFFF)) //4294967295
{
Delay(0.001);
led_count++;
if(led_count>=7)
{
SetCtrlVal (panelHandle, PANEL_LED_rec, 1);
led_count=0;
}
else if(led_count>=3)
{
SetCtrlVal (panelHandle, PANEL_LED_rec, 0);
}
for( int n = 0; n < recLen; n++ )
{
rec[i].TimeStamp=0; //设备接收到某一帧的时间标识。 时间标示从CAN卡上电开始计时,计时单位为0.1ms。
switch(rec[i].ID)
{
case 0x00f07008:
int counter=0;
counter=rec[i].Data[1];
SetCtrlVal (panelHandle, PANEL_Counter,counter);
break;
case 0x00f07108:
Udc_P28=(((rec[i].Data[0]&0x00FF)<<8)+((rec[i].Data[1]&0x00FF)))*0.1;
Idc_P28=(((rec[i].Data[2]&0x00FF)<<8)+(rec[i].Data[3]&0x00FF))*0.1;
Udc_N28=(((rec[i].Data[4]&0x00FF)<<8)+(rec[i].Data[5]&0x00FF))*0.1;
Idc_N28=(((rec[i].Data[6]&0x00FF)<<8)+(rec[i].Data[7]&0x00FF))*0.1;
P_kW=(Udc_P28*Idc_P28)*0.001;
N_kW=(Udc_N28*Idc_N28)*0.001;
kW=P_kW+N_kW;
SetCtrlVal (panelHandle, PANEL_Udc_P,Udc_P28);
SetCtrlVal (panelHandle, PANEL_Idc_P,Idc_P28);
SetCtrlVal (panelHandle, PANEL_P_kW,P_kW);
SetCtrlVal (panelHandle, PANEL_Udc_N,Udc_N28);
SetCtrlVal (panelHandle, PANEL_Idc_N,Idc_N28);
SetCtrlVal (panelHandle, PANEL_N_kW,N_kW);
SetCtrlVal (panelHandle, PANEL_KW,kW);
break;
case 0x00f07208:
double Moterhour=0;
double MoterWaterTemp=0;
double Moteroilpress=0;
double Moterspeed=0;
Moterhour=(((rec[i].Data[0]&0x00FF)<<8)+((rec[i].Data[1]&0x00FF)))*0.1;
MoterWaterTemp=rec[i].Data[2]-50;
Moteroilpress=rec[i].Data[3]*4;
Moterspeed=((rec[i].Data[4]&0x00FF)<<8)+((rec[i].Data[5]&0x00FF));
SetCtrlVal (panelHandle, PANEL_Moterhour,Moterhour);
SetCtrlVal (panelHandle, PANEL_MoterWaterTemp,MoterWaterTemp);
SetCtrlVal (panelHandle, PANEL_Moteroilpress,Moteroilpress);
SetCtrlVal (panelHandle, PANEL_Moterspeed,Moterspeed);
break;
case 0x00f07308:
int WorkState; //发电机工作状态
int Worklight; //发电指示
int GENOverTemp; //发电机过温
int INVOverTemp; //控制器过温
int oilPressAlarm; //油压报警
int OverCurrentAlarm; //过流报警
int OverVoltageAlarm; //过压报警
WorkState=(rec[i].Data[0]&0xc0)>>6;
Worklight=(rec[i].Data[0]&0x30)>>4;
GENOverTemp=(rec[i].Data[1]&0x80)>>7;
INVOverTemp=(rec[i].Data[1]&0x40)>>6;
oilPressAlarm=(rec[i].Data[1]&0x20)>>5;
OverCurrentAlarm=(rec[i].Data[1]&0x10)>>4;
OverVoltageAlarm=(rec[i].Data[1]&0x08)>>3;
if(WorkState==2)
{
SetCtrlVal (panelHandle, PANEL_WorkState,1);
}
else if(WorkState==1)
{
SetCtrlVal (panelHandle, PANEL_MArm,1);
}
else if(WorkState==0)
{
SetCtrlVal (panelHandle, PANEL_WorkState,0);
}
if(Worklight==2)
{
SetCtrlVal (panelHandle, PANEL_Worklight,1);
}
else if (Worklight==1)
{
SetCtrlVal (panelHandle, PANEL_Arm,1);
}
else if(Worklight==0)
{
SetCtrlVal (panelHandle, PANEL_Worklight,0);
}
if(GENOverTemp==1)
{
SetCtrlVal (panelHandle, PANEL_GENOverTemp,1);
}
else
{
SetCtrlVal (panelHandle, PANEL_GENOverTemp,0);
}
if(INVOverTemp==1)
{
SetCtrlVal (panelHandle, PANEL_INVOverTemp,1);
}
else
{
SetCtrlVal (panelHandle, PANEL_INVOverTemp,0);
}
if(oilPressAlarm==1)
{
SetCtrlVal (panelHandle, PANEL_oilPressAlarm,1);
}
else
{
SetCtrlVal (panelHandle, PANEL_oilPressAlarm,0);
}
if(OverCurrentAlarm==1)
{
SetCtrlVal (panelHandle, PANEL_OverCurrentAlarm,1);
}
else
{
SetCtrlVal (panelHandle, PANEL_OverCurrentAlarm,0);
}
if(OverVoltageAlarm==1)
{
SetCtrlVal (panelHandle, PANEL_OverVoltageAlarm,1);
}
else
{
SetCtrlVal (panelHandle, PANEL_OverVoltageAlarm,0);
}
break;
case 0x00f07408:
int a; //
int x; //
int y; //
int year; //年
int moth; //月
int day; //日
int sleftest; //自检信息
a=rec[i].Data[0];
x=rec[i].Data[1];
y=rec[i].Data[2];
year=rec[i].Data[3]+2000;
moth=rec[i].Data[4];
day=rec[i].Data[5];
sleftest=rec[i].Data[6];
SetCtrlVal (panelHandle, PANEL_A,a);
SetCtrlVal (panelHandle, PANEL_X,x);
SetCtrlVal (panelHandle, PANEL_Y,y);
SetCtrlVal (panelHandle, PANEL_Year,year);
SetCtrlVal (panelHandle, PANEL_Moth,moth);
SetCtrlVal (panelHandle, PANEL_Day,day);
SetCtrlVal (panelHandle, PANEL_Sleftest,sleftest);
break;
}
}
}
}
return 0;
}
在PANL_callback 的函数下关闭多线程
int CVICALLBACK PANL_callback (int panel, int event, void *callbackData,
int eventData1, int eventData2)
{
switch (event)
{
case EVENT_GOT_FOCUS:
break;
case EVENT_LOST_FOCUS:
break;
case EVENT_CLOSE:
GetCtrlVal (panelHandle, PANEL_DEVICE_TYPE, &DeviceType);
VCI_CloseDevice(DeviceType,0);
QuitUserInterface (0);
startflag = 0;
CmtWaitForThreadPoolFunctionCompletion (DEFAULT_THREAD_POOL_HANDLE, threadID,0); //立即结束
CmtReleaseThreadPoolFunctionID (DEFAULT_THREAD_POOL_HANDLE, threadID); //释放线程函数
break;
}
return 0;
}
CAN接受函数如下;
int CVICALLBACK CAN_Transmit (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
int GetTransmit;
int statues;
switch (event)
{
case EVENT_COMMIT:
GetCtrlVal (panelHandle, PANEL_DEVICE_TYPE, &DeviceType);
GetCtrlVal (panelHandle, PANEL_CAN_CAHENNEL, &CANChannel);
GetCtrlVal (panelHandle, PANEL_CMD_TRANSMIT, &statues);
send[0].ID= 0x00f06407; //帧ID
send[0].SendType=0; //正常发送
send[0].RemoteFlag=0; //0时为为数据帧
send[0].ExternFlag= 1;//1时为扩展帧
send[0].DataLen=0x08; //数据长度
if(statues==1)
{
send[0].Data[1]=0x01;
for(int i=0;i<100;i++)
{
GetTransmit=VCI_Transmit(DeviceType,0,CANChannel,send,1);
}
}
else
{
send[0].Data[1]=0x02;
for(int i=0;i<100;i++)
{
GetTransmit=VCI_Transmit(DeviceType,0,CANChannel,send,1);
}
}
break;
}
return 0;
}
总结
学会了Labwondows/CVI的程序编译后,在看看《接口函数库(二次开发库)》对照周立功的CVI示例很容易就能写出来一个上位机。不过我的程序存在BUG还需要慢慢修改,个人感觉可能异步定时器要比多线程要好一些吧。
本文地址:https://blog.csdn.net/sinat_42433972/article/details/109275295
上一篇: 菜鸟成长手册:掀翻显卡选购愚民谬论