intel杯初选赛过了,接下来就是区域决赛。准备时间有两个月,时间还是比较紧张,必须在这两个月内把所有的知识都消化掉。接下来的打算是想面试几家公司,试试自己的水准,打好比赛,INtel和蓝桥杯,十二月份开始准备考研。
这里的全部源码是不会贴了,毕竟还要尊重一下平衡小车之家的知识产权,我这里主要把思路说清楚,具体的内容不会写太细
这个图是平衡小车之家配套的app,用来控制底盘移动以及一些参数的调整。收发方式通过蓝牙,分三种模式重力、摇杆、按键,点击屏幕上的按钮,通过蓝牙端发送字符就能控制底盘,图片中Debug下就是发送的字符。
我的想法是先找到通信端,然后找到控制函数,然后完成整个代码的梳理。不过一上来我就懵了,光通信函数都不少呢!串口1、2、3,IIC、PS2、CAN,幸好有注释,要不然真得一个一个找了。
通过注释我们知道,移动端控制底盘主要是通过串口2进行的,因此我们先来看看串口2的中断函数
/**************************************************************************
函数功能:串口2接收中断
入口参数:无
返回 值:无
**************************************************************************/
int USART2_IRQHandler(void) //手机蓝牙发送指令
{
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收到数据
{
static u8 Flag_PID,i,j,Receive[50]; //这些都是静态变量
static float Data;
Usart_Receive=USART2->DR;
if(Usart_Receive==0x4B) Turn_Flag=1; //进入转向控制界面,由它判断模式。1---按键模式
else if(Usart_Receive==0x49||Usart_Receive==0x4A) Turn_Flag=0; //方向控制界面。0---重力和摇杆模式
if(Run_Flag==0)//速度控制模式
{
if(Turn_Flag==0)//速度控制模式
{
if(Usart_Receive>=0x41&&Usart_Receive<=0x48) //速度方向 1-8分别从中上 右上 右中到左上
{
Flag_Direction=Usart_Receive-0x40; //从AB..GH换成数字12..78
}
else if(Usart_Receive<=8)
{
Flag_Direction=Usart_Receive;
}
else Flag_Direction=0;
}
else if(Turn_Flag==1)//如果进入了转向控制界面
{
if(Usart_Receive==0x43) Flag_Left=0,Flag_Right=1; //右转
else if(Usart_Receive==0x47) Flag_Left=1,Flag_Right=0; //左转
else Flag_Left=0,Flag_Right=0; //直走或后退
if(Usart_Receive==0x41||Usart_Receive==0x45)Flag_Direction=Usart_Receive-0x40; //如果是前进或者后退,则将方向为直接设置成数字1、5
else Flag_Direction=0;
}
}
//以下是与APP调试界面通讯
if(Usart_Receive==0x7B) Flag_PID=1; //APP参数指令起始位
if(Usart_Receive==0x7D) Flag_PID=2; //APP参数指令停止位
if(Flag_PID==1) //采集数据
{
Receive[i]=Usart_Receive;
i++;
}
if(Flag_PID==2) //分析数据
{
if(Receive[3]==0x50) PID_Send=1;
else if(Receive[3]==0x57) Flash_Send=1;
else if(Receive[1]!=0x23)
{
for(j=i;j>=4;j--)
{
Data+=(Receive[j-1]-48)*pow(10,i-j);
}
switch(Receive[1])
{
case 0x30: RC_Velocity=Data;break; //速度初始值,基准值
case 0x31: RC_Position=Data;break; //位置初始值,基准值
case 0x32: Position_KP=Data;break;
case 0x33: Position_KI=Data;break;
case 0x34: Position_KD=Data;break;
case 0x35: Velocity_KP=Data;break;
case 0x36: Velocity_KI=Data;break;
case 0x37: break; //预留
case 0x38: break; //预留
}
}
Flag_PID=0;//相关标志位清零
i=0;
j=0;
Data=0;
memset(Receive, 0, sizeof(u8)*50);//数组清零
}
}
return 0;
}
那么中断函数具体完成了什么事情?
1.接收从移动端传来的字符
2.首先是模式判断,分别对应app端的按键模式、重力模式、摇杆模式
3.如果传来的不是模式判断字符,则进行速度方向模式判断
4.如果不是速度模式判断字符,则进行参数调整模式判断
这样就完成了app端对底盘的移动控制!
那么接着往下看,主函数里并没有对底盘的移动控制,只有一些显示函数。那么如何控制的呢?我们自然而然可以想到中断控制
以下是部分主函数图:
最后通过外部中断初始化,我们找到是通过EXTI15_10_IRQHandler来进行控制的,那么接下来我们对这个函数做一下剖析
int EXTI15_10_IRQHandler(void)
{
if(INT==0)
{
EXTI->PR=1<<15; //清除LINE5上的中断标志位
Flag_Target=!Flag_Target;
if(delay_flag==1)
{
if(++delay_50==10) delay_50=0,delay_flag=0; //给主函数提供50ms的精准延时
}
if(Flag_Target==1) //5ms读取一次陀螺仪和加速度计的值
{
if(Usart_Flag==0&&PS2_ON_Flag==0&&Usart_ON_Flag==1) memcpy(rxbuf,Urxbuf,8*sizeof(u8)); //如果解锁了串口控制标志位,进入串口控制模式
Read_DMP(); //===更新姿态
Key();//扫描按键变化
return 0;
} //===10ms控制一次,为了保证M法测速的时间基准,首先读取编码器数据
Encoder_A=Read_Encoder(2)/20; //===读取编码器的值
Position_A+=Encoder_A; //===积分得到速度
Encoder_B=Read_Encoder(3)/20; //===读取编码器的值
Position_B+=Encoder_B; //===积分得到速度
Encoder_C=Read_Encoder(4)/20; //===读取编码器的值
Position_C+=Encoder_C; //===积分得到速度
Read_DMP(); //===更新姿态
Led_Flash(100); //===LED闪烁;常规模式 1s改变一次指示灯的状态
Voltage_All+=Get_battery_volt(); //多次采样累积
if(++Voltage_Count==100) Voltage=Voltage_All/100,Voltage_All=0,Voltage_Count=0;//求平均值 获取电池电压
if(PS2_KEY==4)PS2_ON_Flag=1,CAN_ON_Flag=0,Usart_ON_Flag=0;
if(CAN_ON_Flag==1||Usart_ON_Flag==1||PS2_ON_Flag==1) CAN_N_Usart_Control(); //接到串口或者CAN遥控解锁指令之后,使能CAN和串口控制输入
if(RC_Velocity>0&&RC_Velocity<25) RC_Velocity=25; //避免电机进入低速非线性区
if(Turn_Off(Voltage)==0) //===如果电池电压不存在异常
{
if(Run_Flag==0)//速度模式
{
if(CAN_ON_Flag==0&&Usart_ON_Flag==0&&PS2_ON_Flag==0) Get_RC(Run_Flag); //===串口和CAN控制都未使能,则接收蓝牙遥控指
Motor_A=Incremental_PI_A(Encoder_A,Target_A); //===速度闭环控制计算电机A最终PWM
Motor_B=Incremental_PI_B(Encoder_B,Target_B); //===速度闭环控制计算电机B最终PWM
Motor_C=Incremental_PI_C(Encoder_C,Target_C); //===速度闭环控制计算电机C最终PWM
}
else//位置模式
{
if(CAN_ON_Flag==0&&Usart_ON_Flag==0&&PS2_ON_Flag==0)//===串口和CAN控制都未使能,则接收蓝牙遥控指令
{
if(Turn_Flag==0) Flag_Direction=click_RC();
Get_RC(Run_Flag);
}
Motor_A=Position_PID_A(Position_A,Target_A)>>8;//位置闭环控制,计算电机A速度内环的输入量
Motor_B=Position_PID_B(Position_B,Target_B)>>8;//位置闭环控制,计算电机B速度内环的输入量
Motor_C=Position_PID_C(Position_C,Target_C)>>8;//位置闭环控制,计算电机C速度内环的输入量
if(rxbuf[0]!=2) Count_Velocity(); //这是调节位置控制过程的速度大小
else
Xianfu_Velocity(RC_Velocity,RC_Velocity,RC_Velocity);
Show_Data_Mb=Motor_B;
Motor_A=Incremental_PI_A(Encoder_A,-Motor_A); //===速度闭环控制计算电机A最终PWM
Motor_B=Incremental_PI_B(Encoder_B,-Motor_B); //===速度闭环控制计算电机B最终PWM
Motor_C=Incremental_PI_C(Encoder_C,-Motor_C); //===速度闭环控制计算电机C最终PWM
}
Xianfu_Pwm(6900); //===PWM限幅
Set_Pwm(Motor_A*84/72,Motor_B*84/72,Motor_C*84/72); //===赋值给PWM寄存器
}
}
return 0;
}
接下来就是速度方向、控制
首先就是读编码器的值,读到速度。平均计算得到电压,然后还判断是否用到了其他通信(所以说这里can,和usart1、3实际上是没有用到的),速度是否已经处于低速,电压是否存在异常等。
如果电池电压没有异常,则进行速度控制等。首先就是摇杆模式下的方向控制
switch(Flag_Direction) //方向控制
{
case 1: Move_X=0; Move_Y+=step; Flag_Move=1; break;
case 2: Move_X+=step; Move_Y+=step; Flag_Move=1; break;
case 3: Move_X+=step; Move_Y=0; Flag_Move=1; break;
case 4: Move_X+=step; Move_Y-=step; Flag_Move=1; break;
case 5: Move_X=0; Move_Y-=step; Flag_Move=1; break;
case 6: Move_X-=step; Move_Y-=step; Flag_Move=1; break;
case 7: Move_X-=step; Move_Y=0; Flag_Move=1; break;
case 8: Move_X-=step; Move_Y+=step; Flag_Move=1; break;
default: Flag_Move=0; Move_X=Move_X/1.05f;Move_Y=Move_Y/1.05f; break;
}
Flag_Direction对应相应的方向,通过计算能得出XYZ轴目标速度,然后通过一大堆专业的的运动学分析得到电机目标值。具体就是Kinematic_Analysis(Move_X,Move_Y,Move_Z);//得到控制目标值,进行运动学分析
有了编码器得到的速度值以及目标值,然后通过PI计算出每个电机的PWM变量Motor_A=Incremental_PI_A(Encoder_A,Target_A); ,最后再通过用PWM变量计算出每个电机最终的PWM值Set_Pwm(Motor_A84/72,Motor_B84/72,Motor_C*84/72);
具体的运动学分析这里就没有提及,因为我也没搞懂,而且挺复杂的。后面的工作还有很多,再板子来之前自己还有一两天的准备时间,补补32吧