智能车电磁循迹模块化(进阶)

       大家应该都喜欢模块化后的函数,可以直接调用。我之前学习的时候经过总结,把大部分电磁循迹的函数模块化了,大家以这些为参考,提升自己的理解。

        关于电磁循迹模块化的内容大家可以看看我之前写的博客(16条消息) 智能车电磁循迹模块化(差速舵机通用)_智能车电磁循迹程序_A_Frost的博客-CSDN博客我把PID等算法写成了模块化的函数,尽量使用局部变量和形参,大家可以方便调用。

        电感归一化

好处是增强赛道的适应性

        各电感值最大最小值最后是全局变量,方便全局有时候会用到。

再此之前得先对赛道进行扫描处理


float AD_L_Max=2000,AD_L_Min=1000,AD_R_Max=2000,AD_R_Min=1000;
float AD_M_Max=2000,AD_M_Min=1000;
float AD_LV_Max=100,AD_LV_Min=50,AD_RV_Max=100,AD_RV_Min=50;
void Inductor_Scan()
{
	
	uint16 L_temp[10],R_temp[10],M_temp[10],LV_temp[10],RV_temp[10];
	int i;
	 for(i=0;i<10;i++)  
     {    
		 Inductor_Smooth_Filter_Get_val();//获取电感值,在前一篇博客
		 L_temp[i]=AD_L;
       if(L_temp[i]>AD_L_Max) AD_L_Max=L_temp[i];
	   if(L_temp[i]<AD_L_Min) AD_L_Min=L_temp[i];
	   
		 R_temp[i]=AD_R;
       if(R_temp[i]>AD_R_Max) AD_R_Max=R_temp[i];
       if(R_temp[i]<AD_R_Min)AD_R_Min=R_temp[i];
		
		 M_temp[i]=AD_M;
      if(M_temp[i]>AD_M_Max)AD_M_Max=M_temp[i];
	  if(M_temp[i]<AD_M_Min)AD_M_Min=M_temp[i];
		 		 LV_temp[i]=AD_LV;//L1  AD_L
       if(LV_temp[i]>AD_LV_Max) AD_LV_Max=LV_temp[i];
	   if(LV_temp[i]<AD_LV_Min) AD_LV_Min=LV_temp[i];
	   
		 RV_temp[i]=AD_RV;
       if(RV_temp[i]>AD_RV_Max) AD_RV_Max=RV_temp[i];
       if(RV_temp[i]<AD_RV_Min)AD_RV_Min=RV_temp[i];
     } 	
	
		AD_L=(AD_L-AD_L_Min)/(AD_L_Max-AD_L_Min);
		AD_R=(AD_R-AD_R_Min)/(AD_R_Max-AD_R_Min);
		AD_M=(AD_M-AD_M_Min)/(AD_M_Max-AD_M_Min);
		AD_LV=(AD_LV-AD_LV_Min)/(AD_LV_Max-AD_LV_Min);
		AD_RV=(AD_RV-AD_RV_Min)/(AD_RV_Max-AD_RV_Min);
	
}

之后才是归一化


float AD_L=0,AD_R=0,AD_M=0;
float AD_LE=0;AD_RE=0;
//先对AD_L,AD_M,AD_R赋值之后再调用此函数
void Inductor_Get_Nomolize_val(void)
{	
		AD_L=(AD_L-AD_L_Min)/(AD_L_Max-AD_L_Min);
		AD_R=(AD_R-AD_R_Min)/(AD_R_Max-AD_R_Min);
		AD_M=(AD_M-AD_M_Min)/(AD_M_Max-AD_M_Min);
		AD_LV=(AD_LV-AD_LV_Min)/(AD_LV_Max-AD_LV_Min);
		AD_RV=(AD_RV-AD_RV_Min)/(AD_RV_Max-AD_RV_Min);	
}

这个函数可以在计算控制值之前在主函数调用。

我写过全部都是局部变量形参的,但是说实话这些变量,整个代码都有贯穿,全模块化的函数就不给出了。

串级PID(加速度环)

        对于这种串级pid,写出函数更好理解,对于单一变量处理不易理解。我们一样写一个函数,参数为左右占空比,控制速度。之后再以此函数为基础再写一个速度环函数就行。

我的函数为 CarForward(duty_r,duty_l);(这种函数很好写的,就是填参数控制左右占空比)

(大家可以自己安库函数实现此功能,之前我的那一篇博客里面也有写上面这种函数的思路,可以直接参考。

假设你写好了这个函数 CarForward(duty_r,duty_l);

        计算速度

一般都有库函数直接读取

//编码器
#define SPEEDL_PLUSE   CTIM0_P34
#define SPEEDR_PLUSE   CTIM3_P04
//定义方向引脚
#define L_DIR     P35
#define R_DIR     P53
//我买的是带方向编码器,根据L_DIR,R_DIR的数值就能判断方向,这个可以TFTshow出来,看看反了没
float VeL_actual = 0.0,VeR_actual = 0.0;
//ctimer_count_read   ctimer_count_clean 为库函数大家找找自己的单片机库,或者找厂家要

void  Velocity_Calu()
{
	
	VeL_actual =ctimer_count_read(SPEEDL_PLUSE); //右轮速度
	VeR_actual =ctimer_count_read(SPEEDR_PLUSE); //左轮速度	
	ctimer_count_clean(SPEEDL_PLUSE);//计数器清零
	ctimer_count_clean(SPEEDR_PLUSE);	
	if (L_DIR == 0)VeL_actual = -VeL_actual;//得自己尝试是0还是1
	if (R_DIR == 1)VeR_actual = -VeR_actual;		
}

速度环包含控制占空比函数

        由于输出的占空比有时候可能有用,我把它当成全局变量,如果对于你们来说没啥用可以放函数里面。我之前写过一篇博客有介绍速度环函数。

/**
* @brief 增量式速度环
/*
优点:
①误动作时影响小,必要时可用逻辑判断的方法去掉出错数据。
②手动/自动切换时冲击小,便于实现无扰动切换。当计算机故障时,仍能保持原值。
③算式中不需要累加。控制增量Δu(k)的确定仅与最近3次的关。
缺点:
①积分截断效应大,有稳态误差;
②溢出的影响大。有的被控对象用增量式则不太好;
/*
* @param Goal_A目标左轮速度,Goal_B目标右轮速度
* @retval none
*/
//之后占空比就没用了,只是用编码器的值非线性表示
int16 duty_l=0,duty_r=0;//vr,vl
float V_Kp_L=29.2,V_Ki_L=4.69,V_Kp_R=41.1,V_Ki_R=2.80;//220 35 闭环

void Sudu_Huan_2(float Goal_A, float Goal_B) //增量式 (left,right);
{
	float VeL_out_Kp,VeL_out_Ki,VeR_out_Kp,VeR_out_Ki;
    static float error_l=0,error_r=0;last_error_l=0,last_error_r=0;
	Velocity_Calu();
	error_l = Goal_A - VeL_actual; 
     VeL_out_Kp=V_Kp_L * (error_l - last_error_l);
	 VeL_out_Ki=V_Ki_L * error_l;
	last_error_l=error_l;	
	duty_l=duty_l+VeL_out_Kp + VeL_out_Ki;
	
	error_r = Goal_B - VeR_actual; 
     VeR_out_Kp=V_Kp_R * (error_r - last_error_r);
	 VeR_out_Ki=V_Ki_R * error_r;
	last_error_r=error_r;	
	duty_r=duty_r+VeR_out_Kp + VeR_out_Ki;
      if(duty_l<-9000)duty_l=-9000;       
      if(duty_l>9000) duty_l=9000;
	  if(duty_r<-9000)duty_r=-9000;       
      if(duty_r>9000) duty_r=9000;
    CarForward(duty_r,duty_l);
		
}

方向环串速度环

之前忘记给方向环串速度环的函数了,现在加进去

//双电感控制算法
float Inductor_Fllow_Trail_Control_Val_2(float AD_input_L,float AD_input_R,float P,float D)
{
	static float last_error=0;
	float error;
	float output;
	error=(AD_input_R-AD_input_L)/(AD_input_L+AD_input_R);
	output=(P*error+D*(error-last_error));
	last_error=error;
	return output;
}

void Fllow_Trail_Done(float Vl0,float Vr0)
{ 			
	float Inductance_Control_val=0;
	Inductor_Getval();
	if(fg.Horizontal_Change_To_Vertical_Inductor_Fllow_Trail==0){
//	Inductance_Control_val=Inductor_Fllow_Trail_Control_Val(AD_L,AD_R,AD_M,I_Kp,I_Kd);//横三电感循迹
		Inductance_Control_val=Inductor_Fllow_Trail_Control_Val_2(AD_L,AD_R,I_Kp,I_Kd);//横双电感循迹
	}
	if(fg.Horizontal_Change_To_Vertical_Inductor_Fllow_Trail==1){
	Inductance_Control_val=Inductor_Fllow_Trail_Control_Val(AD_LV,AD_RV,AD_M,I_Kp_V,I_Kd_V);//竖三电感循迹
	}
	Correct_Val=Inductance_Control_val;
	//差速循迹
		
	Speed_Right=Vr0-Correct_Val;
	Speed_Left=Vl0+Correct_Val;
	
		if(fg.sudu_huan_2==1){//闭环
		if(Correct_Val>=2000) Correct_Val=2000;
		else if(Correct_Val<=-2000) Correct_Val=-2000;
		if(Speed_Right>=2000) Speed_Right=2000;
		if(Speed_Left>=2000) Speed_Left=2000;
	Sudu_Huan_2(Speed_Left,Speed_Right);//增量式速度环
	}
}

多段控制

        有时候我们需要分段控制,多段控制的方法如下

先定义一些控制变量扔结构体里面,方面加很多标志位,写在头文件里面,里面你们很多是没用的。可以自行删减,我有时候并没有用到那么多标志位,多弄点,之后标志位多了再删去。方便修改。

typedef struct{
	//出界
	uint8 chujie;
	//出界不停车
	uint8 Chujie_Not_Stop;
	uint8 qishiwei,qscount_1,qscount_2;
	//菜单打开
	uint8 menu_open;
	//电感处理
	uint8 guiyi_hua,already_scan,gui_zhong,sao_miao;
	//坡道
	uint8 podao_1,podao_2,podao_3;
	//速度环
	uint8 sudu_huan,sudu_huan_2;
	//圆环
	uint8 yhcountL_1,yhcountL_2,yhcountL_3,yhcountL_4,yhcountL_5;
	uint8 yhcountR_1,yhcountR_2,yhcountR_3,yhcountR_4,yhcountR_5;
    uint8 yh_openL,yh_openR;
	//入库
	uint8 stop_1,stop_2,stop_3,already_ru,stop_4,stop_5,stop_6,stop_7,stop_8;
	uint8 ruku_open;
	//出库
	uint8 go_0,go_1,go_2,go_3,already_chu,L_chu,R_chu;uint8 chuku_open;
	uint16 go_ph_1,stop_ph_1;//平滑转弯
	//左右出入库控制
	uint8 chuku_L,chuku_R;
	//避障
	uint8 bz_1,bz_2,bz_3,bz_4,bz_5,bz_6,bz_7,bz_8,bz_9;
	uint8 bz_L,bz_R, bz_open;
	uint8 already_bz;
	//编码器积分测量测量,
	uint8 integral_ecod_open;
	//陀螺仪积分测量
	uint8 integral_gyro_open;
	uint8 Quaternion_Euler_angles_open;
	//历时测量
	float counter;uint8 time_open;
	//激光测距
	uint8 Jkg_open;
	//开关循迹
	uint8 xunji,Fllow_Trail;
		//转固定角启动位
		uint8 Turn_engle_Enable;
		//走固定时间启动位
		uint8 Go_Set_Time_Enable;
		//走固定路程启动位
		uint8 Go_Set_Distance_Enable;
		//for 进去
		uint8 For_To_Go;
	//蓝牙拟狗叫器
	uint16 Bt_counter;
	//切换竖电感循迹
	uint8 Horizontal_Change_To_Vertical_Inductor_Fllow_Trail;
	//确定速度
	uint8 Velocity_Have_Determined;
	//防止疯转
	uint8 Is_Crazy,Is_Crazy_Count;
	//单独获取电感值
	uint8 Obtained_Inductance_Separately;
	//堵转*
	uint8 Is_Stuck_In;	
}my;

之后定义结构体变量

my fg;//就是Flag我缩写了

我是使用if while 的形式多弄标志位就可以实现多段处理。

比如出库函数我是这样写的

中断函数里面


float distance_traveled,turn_engle;
//下面的东西放中断
if(fg.integral_ecod_open==1){
	Velocity_Calu();
	distance_traveled+=(VeL_actual+VeR_actual)/200;	
	}
else if(fg.integral_ecod_open==0)distance_traveled=0;


if(fg.integral_gyro_open==1){
	Get_Gyroval();		
	turn_engle+=gyro_z;//	
	}else if(fg.integral_gyro_open==0)turn_engle=0;

之后写一个模块化的函数随便在什么地方,通过标准位的关开就可以控制这个函数。


/**
* @brief 直接控制占空比走固定路程
* @param set_dutyL左占空比 set_dutyR右占空比 set_distance设定长度
/*@usuage fg.Go_Set_Distance_Enable=1;
	while(flag.next){
	Go_Fixed_Distance_Done(2,2000,2000);
		if(fg.Go_Set_Distance_Enable==0){
			//next Done
		}
	}
/*
* @retval none
*/

void Go_Fixed_Distance_Done(uint16 set_dutyL,uint16 set_dutyR,float set_distance)
//要关掉中断的循迹
{
	
	if(fg.Go_Set_Distance_Enable==1){
			fg.integral_ecod_open=1;
			if(distance_traveled<set_distance){
			CarForward(set_dutyR,set_dutyL);
			}
			else {
				fg.Go_Set_Distance_Enable=0;
				fg.integral_ecod_open=0;
			}
	}	
}





/**
* @brief 陀螺仪转向pid函数—>使差速车拥有类似舵机的转向
* @param Set_engle 设置角度负数右转,正数左转
Faster_Speed 快的速度,Slower_Speed 慢的速度 P D为参数
/*@usuage 
	fg.Turn_engle_Enable=1;
	在上一个判断置一,下一个循环执行转向函数
	Turn_engle_PD_Done(7000,3000,800,0.2,0.3);
/*
* @retval 无
*/
float PD_engle_val=0;
uint8 Control_enable_L,Control_enable_R;
void Turn_engle_PD_Done(float Set_engle,float Faster_Speed,float Slower_Speed,float P,float D)//要关闭中断的循迹
{
	static float last_engle_error=0;
	float engle_error;
	if(fg.Turn_engle_Enable==1){
//		fg.xunji=0;
		Out_Of_Bounds_Control();//
		fg.integral_gyro_open=1;
	if(Set_engle>0){//0开始变化 向左转动 左减右加
			if(turn_engle<Set_engle){
				engle_error=Set_engle-turn_engle;
				PD_engle_val=P*engle_error+D*(engle_error-last_engle_error);
				last_engle_error=engle_error;
				Control_enable_L=1;
				}
			else if(turn_engle>Set_engle){
				last_engle_error=0;	
				Control_enable_L=0;
				}
	}
	if(Set_engle<0){//-9000 now -6000//右转,左加右减
			if(turn_engle>Set_engle){
				engle_error=Set_engle-turn_engle;
				PD_engle_val=P*engle_error+D*(engle_error-last_engle_error);
				last_engle_error=engle_error;
				Control_enable_R=1;
				}
			else if(turn_engle<Set_engle){
				last_engle_error=0;	
				Control_enable_R=0;
				}
	}
	if(PD_engle_val>8000)PD_engle_val=8000;
	else if(PD_engle_val<-8000)PD_engle_val=-8000;

	if(Control_enable_L==1)//左转
	CarForward(Faster_Speed+PD_engle_val,Slower_Speed-PD_engle_val);//右加 左减
	else if(Control_enable_R==1)//右转
	CarForward(Slower_Speed+PD_engle_val,Faster_Speed-PD_engle_val);//R L
	
	if(Control_enable_R==0 ||Control_enable_L==0){
		fg.Turn_engle_Enable=0;//
		fg.integral_gyro_open=0;
//		CarForward(0,0);
	}
	}			

当然也可以不模块化,按照下面的思路写也行。我差速车,控制的角度没四轮那么方便,上面的模块化大家看看就行,可以模仿写出类似的函数。

思路就是一段一段打开关闭标志位,循迹放中断。

大家可以看看我出入库的函数,以做参考

void chu_ku()//出库
{
	
	if(fg.already_chu==0){
		fg.xunji=0;
		fg.go_1=1;
		fg.Go_Set_Distance_Enable=1;
		while(fg.go_1){
			Go_Fixed_Distance_Done(3500,3500,800);
			if(fg.Go_Set_Distance_Enable==0){
			fg.go_1=0;
			fg.go_2=1;
			fg.Turn_engle_Enable=1;
			}	
		}
		while(fg.go_2){
			if(fg.chuku_R==1){//右出库				
			Turn_engle_PD_Done(-8000,3000,500,0.2,0.03);
			if(fg.Turn_engle_Enable==0){
			fg.go_2=0;
			fg.go_3=1;
			}	
			}
			else if(fg.chuku_L==1){
			Turn_engle_PD_Done(8000,3000,500,0.2,0.03);
			if(fg.Turn_engle_Enable==0){
				fg.go_2=0;
				fg.go_3=1;
			}	
			}			
		}
		if(fg.go_3==1){
			 fg.go_3=0;
			fg.already_chu=1;
			fg.xunji=1;
		}
	}
}

        

void Dao_Che_Ru_Ku()//参数不好调时,用多过程
{	//	//入库
	if(fg.already_chu==1){
	if(fg.qishiwei==1&&fg.stop_1==0&&fg.stop_2==0)
	{
		fg.stop_1=1;
		fg.xunji=0;
		fg.Go_Set_Time_Enable=1;
	}	
	while(fg.stop_1){
	Go_Set_Time_Done(-2000,-2000,15);
		if(fg.Go_Set_Time_Enable==0){
		fg.stop_1=0;
		fg.stop_2=1;
			fg.Turn_engle_Enable=1;
		}
	}
	while(fg.stop_2){
			Turn_engle_PD_Done(-2500,3000,-4000,0.2,0.03);
		if(fg.Turn_engle_Enable==0){
		fg.stop_2=0;
		fg.stop_3=1;
			fg.Go_Set_Time_Enable=1;
		}		
	}
	while(fg.stop_3){				
		if(fg.stop_3==1)Go_Set_Time_Done(-4000,8000,15);
		if(fg.Go_Set_Time_Enable==0){
		fg.stop_3=0;
		fg.stop_4=1;
			fg.Go_Set_Time_Enable=1;
		}			
	}
	while(fg.stop_4){
		if(fg.stop_4==1)Go_Set_Time_Done(-2000,2000,2);
		if(fg.Go_Set_Time_Enable==0){
		fg.stop_4=0;
		fg.stop_5=1;			
		}
	}
	while(fg.stop_5){
		
	CarForward(0,0);
	}
}	
}	

        模块化的写法确实好用,但是大家自己写的时候注意局部变量和静态变量的调用,有什么好的模块化代码也可以分享出来造福大家。

  • 9
    点赞
  • 95
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值