欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

Labwindows/CVI 编写CAN通讯的上位机

程序员文章站 2022-07-05 21:30:09
Labwindows/CVI 编写CAN通讯的上位机前言 本人从事电机测试已久,会编写DSP的CAN通讯程序编写,但是一直对上位机的编译学不会。学习了很长一段时间的C#但是还是没有弄明白动态链接库和委托,最后我公司来了一位软件经理指导我一下,劝我放弃C#转学习Labwindows/CVI这样一来既可以学习C语言又可以学习上位机。通过对比我发现CVI确实比C#更容易一些,接下来我就将我写的过程分享一下。一、Labwindows如何学习 CVI学习起来很简单,如果有C语言的基础完全没有问题,...

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