[开发进程]实验十、另类走8字
背景:
上文通过对左右编码器采样出来的数据进行积分(distance = distance + (float)Encoder_Left*0.005;)得到了小车前进的距离。
其中distance是自己定义的小车前进距离。
那么,同样的道理,可以通过对左右编码器之差进行积分turning = turning + (float)(Encoder_Left-Encoder_Right)*0.005;,得到小车转弯的大概的角度。
笔者试验室的地板是方块的,60cm*60cm,那么为什么不可以走方形的八字呢。这种八字就是数码管的8。
原理:
本试验的原理很简单,要使车子走数码管8字,只需要使小车按照这个路线图走就可以了。
只是调试过程比较痛苦……
实验代码
这个实验基于<Mini Balance V2.5 标准版源码卡尔曼版本>
修改了两个源文件。分别是show.c和MiniBalance.c
以下是这两个修改后的文件的代码:
//show.c #include "show.h" #include "MiniBalance.h" /************************************************************************** 作者:平衡小车之家 淘宝店铺:http://shop114407458.taobao.com/ **************************************************************************/ unsigned char i; //计数变量 unsigned char Send_Count; //串口需要发送的数据个数 /************************************************************************** 函数功能:OLED显示 入口参数:无 返回 值:无 作 者:平衡小车之家 **************************************************************************/ static u32 Count; void oled_show(void) { int disSHow; int turSHow; Count=0; OLED_Display_On(); //显示屏打开 //turSHow = turning*1.087613; turSHow = turning; disSHow = -distance*2.176542; if(ThreeSecFlag==1) { if(disSHow<300) { Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;} else if(turSHow<85) { Flag_Qian=0,Flag_Hou=0,Flag_Left=1,Flag_Right=0;} else if(disSHow<900) { Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;} else if(turSHow<175) { Flag_Qian=0,Flag_Hou=0,Flag_Left=1,Flag_Right=0;} else if(disSHow<1300) { Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;} else if(turSHow<274) { Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=1;} else if(disSHow<2000) { Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;} else if(turSHow<373) { Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=1;} else if(disSHow<2700) { Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;} else if(turSHow<472) { Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=1;} else if(disSHow<3300) { Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;} else if(turSHow<540) { Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=1;} else if(disSHow<3900) { Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;} else if(turSHow<625) { Flag_Qian=0,Flag_Hou=0,Flag_Left=1,Flag_Right=0;} else if(disSHow<4400) { Flag_Qian=1,Flag_Hou=0,Flag_Left=0,Flag_Right=0;} else if(turSHow<715) { Flag_Qian=0,Flag_Hou=0,Flag_Left=1,Flag_Right=0;} else Flag_Qian=0,Flag_Hou=0,Flag_Left=0,Flag_Right=0; } if(disSHow<0) { OLED_ShowString(0,0,"Dist -"); OLED_ShowNumber(45,0, (-disSHow),5,12); } else { OLED_ShowString(0,0,"Dist "); OLED_ShowNumber(45,0, disSHow,5,12); } if(turSHow<0) { OLED_ShowString(0,10,"Turn -"); OLED_ShowNumber(45,10, (-turSHow),5,12); } else { OLED_ShowString(0,10,"Turn "); OLED_ShowNumber(45,10, turSHow,5,12); } //=============显示温度=======================// /* OLED_ShowString(00,10,"Wendu"); OLED_ShowNumber(45,10,Temperature/10,2,12); OLED_ShowNumber(68,10,Temperature%10,1,12); OLED_ShowString(58,10,"."); OLED_ShowString(80,10,"`C"); */ //=============显示编码器1=======================// OLED_ShowString(00,20,"Enco1"); if( Encoder_Left<0) OLED_ShowString(45,20,"-"), OLED_ShowNumber(65,20,-Encoder_Left,5,12); else OLED_ShowString(45,20,"+"), OLED_ShowNumber(65,20, Encoder_Left,5,12); //=============显示编码器2=======================// OLED_ShowString(00,30,"Enco2"); if(Encoder_Right<0) OLED_ShowString(45,30,"-"), OLED_ShowNumber(65,30,-Encoder_Right,5,12); else OLED_ShowString(45,30,"+"), OLED_ShowNumber(65,30,Encoder_Right,5,12); //=============显示电压=======================// OLED_ShowString(00,40,"Volta"); OLED_ShowString(58,40,"."); OLED_ShowString(80,40,"V"); OLED_ShowNumber(45,40,Voltage/100,2,12); OLED_ShowNumber(68,40,Voltage%100,2,12); if(Voltage%100<10) OLED_ShowNumber(62,40,0,2,12); //=============显示角度=======================// OLED_ShowString(0,50,"Angle"); if(Angle_Balance<0) OLED_ShowNumber(45,50,Angle_Balance+360,3,12); else OLED_ShowNumber(45,50,Angle_Balance,3,12); //=============刷新=======================// OLED_Refresh_Gram(); } /************************************************************************** 函数功能:虚拟示波器往上位机发送数据 关闭显示屏 入口参数:无 返回 值:无 作 者:平衡小车之家 **************************************************************************/ void DataScope(void) { if(++Count==1) { OLED_Clear(); OLED_Display_Off(); } DataScope_Get_Channel_Data( Angle_Balance, 1 ); DataScope_Get_Channel_Data( Encoder_Right, 2 ); DataScope_Get_Channel_Data( Encoder_Left, 3 ); DataScope_Get_Channel_Data( Voltage , 4 ); DataScope_Get_Channel_Data(0, 5 ); //用您要显示的数据替换0就行了 DataScope_Get_Channel_Data(0 , 6 );//用您要显示的数据替换0就行了 DataScope_Get_Channel_Data(0, 7 ); DataScope_Get_Channel_Data( 0, 8 ); DataScope_Get_Channel_Data(0, 9 ); DataScope_Get_Channel_Data( 0 , 10); Send_Count = DataScope_Data_Generate(10); for( i = 0 ; i < Send_Count; i++) { while((USART1->SR&0X40)==0); USART1->DR = DataScope_OutPut_Buffer[i]; } delay_ms(50); //20HZ }
//MiniBalance.c #include "MiniBalance.h" #include "math.h" #include "led.h" #include "mpu6050.h" #define PI 3.14159265 /************************************************************************** 作者:平衡小车之家 淘宝店铺:http://shop114407458.taobao.com/ **************************************************************************/ /************************************************************************** 函数功能:5MS定时中断函数 5MS控制周期 入口参数:无 返回 值:无 作 者:平衡小车之家 **************************************************************************/ int Balance_Pwm,Velocity_Pwm,Turn_Pwm; int FiveMSCount=0; int ThreeSecFlag=0; void TIM1_UP_TIM16_IRQHandler(void) { if(TIM1->SR&0X0001)//5ms定时中断 { FiveMSCount++; if(FiveMSCount==600) ThreeSecFlag=1; if(ThreeSecFlag==1) { distance = distance + (float)Encoder_Left*0.005; if((Encoder_Left-Encoder_Right)>0) turning = turning + (float)(Encoder_Left-Encoder_Right)*0.005; else turning = turning + (float)(Encoder_Right-Encoder_Left)*0.005; } TIM1->SR&=~(1<<0); //===清除定时器1中断标志位 readEncoder(); //===读取编码器的值 Led_Flash(400); //===LED闪烁; Get_battery_volt(); //===获取电池电压 key(100); //===扫描按键状态 Get_Angle(Way_Angle); //===更新姿态 Balance_Pwm =balance(Angle_Balance,Gyro_Balance); //===平衡PID控制 Velocity_Pwm=velocity(Encoder_Left,Encoder_Right); //===速度环PID控制 Turn_Pwm =turn(Encoder_Left,Encoder_Right,Gyro_Turn); //===转向环PID控制 Moto1=Balance_Pwm+Velocity_Pwm-Turn_Pwm; //===计算左轮电机最终PWM Moto2=Balance_Pwm+Velocity_Pwm+Turn_Pwm; //===计算右轮电机最终PWM Xianfu_Pwm(); //===PWM限幅 if(Turn_Off(Angle_Balance,Voltage)==0) //===如果不存在异常 Set_Pwm(Moto1,Moto2); //===赋值给PWM寄存器 } } /************************************************************************** 函数功能:直立PD控制 入口参数:角度、角速度 返回 值:直立控制PWM 作 者:平衡小车之家 **************************************************************************/ int balance(float Angle,float Gyro) { float Bias; int balance; Bias=Angle+0; //===求出平衡的角度中值 和机械相关 +0意味着身重中心在0度附近 如果身重中心在5度附近 那就应该减去5 balance=35*Bias+Gyro*0.125;//===计算平衡控制的电机PWM PD控制 return balance; } /************************************************************************** 函数功能:速度PI控制 入口参数:左轮编码器、右轮编码器 返回 值:速度控制PWM 作 者:平衡小车之家 **************************************************************************/ int velocity(int encoder_left,int encoder_right) { static int Velocity,Encoder_Least,Encoder,Movement; static int Encoder_Integral; //=============遥控前进后退部分=======================// if(1==Flag_Qian) Movement=-700; //===如果前进标志位置1 位移为负 else if(1==Flag_Hou) Movement=700; //===如果后退标志位置1 位移为正 else Movement=0; //=============速度PI控制器======================// Encoder_Least =Encoder_Left+Encoder_Right; //===获取最新速度偏差 Encoder *= 0.8; //===一阶低通滤波器 Encoder += Encoder_Least*0.2; //===一阶低通滤波器 if(Turn_Off(Angle_Balance,Voltage)==0) //为了防止积分影响用户体验,只有电机开启的时候才开始积分 { Encoder_Integral +=Encoder; //===积分出位移 积分时间:5ms Encoder_Integral=Encoder_Integral-Movement; //===接收遥控器数据,控制前进后退 } if(Encoder_Integral>360000) Encoder_Integral=360000; //===积分限幅 if(Encoder_Integral<-360000) Encoder_Integral=-360000; //===积分限幅 Velocity=Encoder*4+Encoder_Integral/140; //===速度PI控制器 if(Turn_Off(Angle_Balance,Voltage)==1) Encoder_Integral=0; //===电机关闭后清除积分 return Velocity; } /************************************************************************** 函数功能:转向PD控制 入口参数:左轮编码器、右轮编码器、Z轴陀螺仪 返回 值:转向控制PWM 作 者:平衡小车之家 **************************************************************************/ int turn(int encoder_left,int encoder_right,float gyro)//转向控制 { static int Turn_Target,Turn,Encoder_temp,Turn_Convert=3,Turn_Count; //int Turn_Bias,Turn_Amplitude=1500/Way_Angle+800; //===Way_Angle为滤波方法,当是1时,即由DMP获取姿态,Turn_Amplitude取大,卡尔曼和互补是,取小,因为这两种滤波算法效果稍差。 int Turn_Bias,Turn_Amplitude=700; static long Turn_Bias_Integral; //=============遥控左右旋转部分=======================// if(1==Flag_Left||1==Flag_Right) //这一部分主要是根据旋转前的速度调整速度的起始速度,增加小车的适应性 { if(++Turn_Count==1) Encoder_temp=myabs(encoder_left+encoder_right); Turn_Convert=2000/Encoder_temp; if(Turn_Convert<3)Turn_Convert=3; if(Turn_Convert>10)Turn_Convert=10; } else { Turn_Convert=3; Turn_Count=0; Encoder_temp=0; } if(1==Flag_Left) Turn_Target+=Turn_Convert; //左转 else if(1==Flag_Right) Turn_Target-=Turn_Convert; //右转 else Turn_Target=0; //停止 if(Turn_Target>Turn_Amplitude) Turn_Target=Turn_Amplitude; //===转向速度限幅 if(Turn_Target<-Turn_Amplitude) Turn_Target=-Turn_Amplitude; //=============转向PD控制器=======================// Turn_Bias=Encoder_Left-Encoder_Right; //===计算转向速度偏差 if(Turn_Off(Angle_Balance,Voltage)==0) //为了防止积分影响用户体验,只有电机开启的时候才开始积分 { Turn_Bias_Integral+=Turn_Bias; //转向速度偏差积分得到转向偏差 Turn_Bias_Integral-=Turn_Target; //获取遥控器数据 } if(Turn_Bias_Integral>1800) Turn_Bias_Integral=1800; //===积分限幅 if(Turn_Bias_Integral<-1800) Turn_Bias_Integral=-1800; //===积分限幅 Turn=Turn_Bias_Integral*2+gyro/12; //===结合Z轴陀螺仪进行PD控制 return Turn; } /************************************************************************** 函数功能:赋值给PWM寄存器 入口参数:左轮PWM、右轮PWM 返回 值:无 作 者:平衡小车之家 **************************************************************************/ void Set_Pwm(int moto1,int moto2) { if(moto1<0) AIN2=1, AIN1=0; else AIN2=0, AIN1=1; PWMA=myabs(moto1); if(moto2<0) BIN1=0, BIN2=1; else BIN1=1, BIN2=0; PWMB=myabs(moto2); } /************************************************************************** 函数功能:读取编码器的数据并进行数据类型转换 入口参数:无 返回 值:无 作 者:平衡小车之家 **************************************************************************/ void readEncoder(void) { u16 Encoder_L,Encoder_R; //===左右编码器的脉冲计数 Encoder_R = TIM4 -> CNT; //===获取正交解码1数据 TIM4 -> CNT=0; //===计数器清零 Encoder_L= TIM2 -> CNT; //===获取正交解码2数据 TIM2 -> CNT=0; //===计数器清零 if(Encoder_L>32768) Encoder_Left=Encoder_L-65000; else Encoder_Left=Encoder_L; //===数据类型转换 Encoder_Left=-Encoder_Left; if(Encoder_R>32768) Encoder_Right=Encoder_R-65000; else Encoder_Right=Encoder_R;//===数据类型转换 } /************************************************************************** 函数功能:限制PWM赋值 入口参数:无 返回 值:无 作 者:平衡小车之家 **************************************************************************/ void Xianfu_Pwm(void) { int Amplitude=3500; //===PWM满幅是3600 限制在3500 if(Moto1<-Amplitude) Moto1=-Amplitude; if(Moto1>Amplitude) Moto1=Amplitude; if(Moto2<-Amplitude) Moto2=-Amplitude; if(Moto2>Amplitude) Moto2=Amplitude; } /************************************************************************** 函数功能:异常关闭电机 入口参数:倾角和电压 返回 值:1:异常 0:正常 作 者:平衡小车之家 **************************************************************************/ u8 Turn_Off(float angle, int voltage) { u8 temp; if(angle<-40||angle>40||1==Flag_Stop||Voltage<1110)//===电压低于11.1V 关闭电机 { //===倾角大于40度关闭电机 temp=1; //===Flag_Stop置1关闭电机 AIN1=0; //===可自行增加主板温度过高时关闭电机 AIN2=0; BIN1=0; BIN2=0; } else temp=0; return temp; } /************************************************************************** 函数功能:获取角度 入口参数:获取角度的算法 1:无 2:卡尔曼 3:互补滤波 返回 值:无 作 者:平衡小车之家 **************************************************************************/ void Get_Angle(u8 way) { float Accel_Y,Accel_X,Accel_Z,Gyro_Y,Gyro_Z; if(way==1) //DMP没有涉及到严格的时序问题,在主函数读取 { } else { Gyro_Y=(I2C_ReadOneByte(devAddr,MPU6050_RA_GYRO_YOUT_H)<<8)+I2C_ReadOneByte(devAddr,MPU6050_RA_GYRO_YOUT_L); //读取Y轴陀螺仪 Gyro_Z=(I2C_ReadOneByte(devAddr,MPU6050_RA_GYRO_ZOUT_H)<<8)+I2C_ReadOneByte(devAddr,MPU6050_RA_GYRO_ZOUT_L); //读取Z轴陀螺仪 Accel_X=(I2C_ReadOneByte(devAddr,MPU6050_RA_ACCEL_XOUT_H)<<8)+I2C_ReadOneByte(devAddr,MPU6050_RA_ACCEL_XOUT_L); //读取X轴加速度记 Accel_Z=(I2C_ReadOneByte(devAddr,MPU6050_RA_ACCEL_ZOUT_H)<<8)+I2C_ReadOneByte(devAddr,MPU6050_RA_ACCEL_ZOUT_L); //读取Z轴加速度记 if(Gyro_Y>32768) Gyro_Y-=65536; //数据类型转换 if(Gyro_Z>32768) Gyro_Z-=65536; //数据类型转换 if(Accel_X>32768) Accel_X-=65536; //数据类型转换 if(Accel_Z>32768) Accel_Z-=65536; //数据类型转换 Gyro_Balance=-Gyro_Y; //更新平衡角速度 Accel_Y=atan2(Accel_X,Accel_Z)*180/PI; //计算与地面的夹角 Gyro_Y=Gyro_Y/16.4; //陀螺仪量程转换 if(Way_Angle==2) Kalman_Filter(Accel_Y,-Gyro_Y);//卡尔曼滤波 else if(Way_Angle==3) Yijielvbo(Accel_Y,-Gyro_Y); //互补滤波 Angle_Balance=angle; //更新平衡倾角 Gyro_Turn=Gyro_Z; //更新转向角速度 } }
而后在MiniBalance.h中放入extern int ThreeSecFlag;即可。
实验视频在审核,估计得在下一楼再 放出。。