这次竞赛选择了沁恒公司的CH32V307芯片作为主控芯片,这个芯片讲道理,在现在这个国内环境下,STM32价格水涨船高,这款芯片的性能144MHz的主频也是可以做很多事情的,几乎可以完全代替STM32,甚至有些方面优于STM32的一些种类芯片。
上面这个框架大家也是可以看到功能还是很全的,比起STC系列的简直不是强的一点半点。
当然还是有一点缺点的,不过如果硬件电路设计的好,这些缺点也就没有什么了。
而且SRAM的大小可以调整,最大128KB,讲真,这么大的内存,代码随便写好吧!
用这款芯片控制起来还是蛮容易的,首先外设很充足,想要干什么都可以,所以我们加了不少模块,GPS、陀螺仪、语音模块、蓝牙什么的。引脚多,外设充足,相互间还可以映射,真的好用。主要的控制用的是串级PID的方式,这款芯片主频很高,定时也很准,各种控制方式都可以往上面加,用起来很爽。
这个芯片的生态可能刚开始发展吧,可能比不上STM32,不过用起来还是很舒服的,和STM32差不多,而且今年的这个307比去年的103强上去不少,去年的好像只支持3级还是2级中断嵌套,今年已经七八级都可以了,而且SRAM也大了不少,记得去年用他们的芯片还很难受,今年的这款已经很舒服了,他们的发展速度可太快了。
他们的开发用的是专门的编译器,用起来的感觉还不错吧,而且可以进行代码优化,运行速度直接翻倍!真的很爽。
下面说说我们用这款芯片都干了些什么吧。
总的来说,就是把轮式机器人的轮子放大成为球壳,变成个球做运动。
大概长的样子就这样:
由于球型机器人的球壳很容易设计为密封球壳,借助其密封的特性可以实现两栖越野在有溪流水洼的草原上或者沙尘较多的环境中进行巡逻检查。并且球型机器人的球壳经过特殊设计,可以开发更多特殊功能,比如在球壳上加上伪装使其在战场进行隐蔽探查,其内部还可以携带爆破用的炸药借助隐蔽特性靠近敌人。也可以借助其抗冲击的特性使其在人流量大的场所进行巡检,可以避免引人群拥挤使机器翻滚无法正常工作。也可以当做益智玩具,球形外壳无棱角,也不易对小孩、室内家具等产生伤害。
可能还有更多的做法或者好用的地方,我没想到,大家也可以一起讨论。
内部机械结构示意图如下
机械结构 1-右侧驱动电机,2-左侧驱动电机,3-动量轮,4-电池及配重,5-工作平台,6、舵机及其摆轴
有点抽象,不过应该还是很明白的吧。
调试的时候为了方便还弄了个上位机,大概长这个样子:
下面是PID的代码,因为是要串起来的,所以可能写法有些奇怪。
//-------------------------------------------------------------------------------------------------------------------
// @brief 电机PID(多级通用)
// @param motor 选择电机
// @return int16
// Sample usage: Motor_PID(RIGHT_MOTOR);
//-------------------------------------------------------------------------------------------------------------------
int16 Motor_PID(Motor_enum motor) //位置式PID程序设计
{
int16 Motor_output_final=0,error=0; //PID调速输出的占空比
long P_data=0,I_data=0,D_data=0,Motor_output=0;
if(motor==Motor_forward) //前进方向
{
error=PID_F.Speed_Target-PID_F.Speed;
if(error<=DEAD_AREA_F && error>=-DEAD_AREA_F) //死区
error=0;
P_data=(long)PID_F.P*error;
PID_F.I_count+=error;
if(PID_F.I_count>I_COUNT_MAX*8) PID_F.I_count=I_COUNT_MAX*8; //限幅
else if(PID_F.I_count<-I_COUNT_MAX*8) PID_F.I_count=-I_COUNT_MAX*8;
I_data=(long)PID_F.I_count*PID_F.I;
// if(error>PID_I_ON*5 || error<-PID_I_ON*5) //积分分离
// I_data=0;
D_data=(long)PID_F.D*(error-PID_F.error_last);
PID_F.error_last=error; //更新误差
Motor_output=(P_data+I_data+D_data)/100;
if(Motor_output>ANGLE_MAX/2) Motor_output=ANGLE_MAX/2; //限幅
else if(Motor_output<-ANGLE_MAX/2) Motor_output=-ANGLE_MAX/2;
}
else if(motor==Motor_forward_w) //前进方向
{
error=PID_F_w.Speed_Target-PID_F_w.Speed;
if(error<=DEAD_AREA_F_W && error>=-DEAD_AREA_F_W) //死区
error=0;
P_data=(long)PID_F_w.P*error;
PID_F_w.I_count+=error;
if(PID_F_w.I_count>I_COUNT_MAX) PID_F_w.I_count=I_COUNT_MAX; //限幅
else if(PID_F_w.I_count<-I_COUNT_MAX) PID_F_w.I_count=-I_COUNT_MAX;
I_data=(long)PID_F_w.I_count*PID_F_w.I;
D_data=(long)PID_F_w.D*(error-PID_F_w.error_last);
PID_F_w.error_last=error; //更新误差
Motor_output=P_data+I_data+D_data;
if(Motor_output>(int)(PWM_DUTY_MAX*battery_protct_ratio)) Motor_output=(int)(PWM_DUTY_MAX*battery_protct_ratio); //限幅
else if(Motor_output<-(int)(PWM_DUTY_MAX*battery_protct_ratio)) Motor_output=-(int)(PWM_DUTY_MAX*battery_protct_ratio);
}
else if(motor==Motor_forward_angle) //前进方向
{
error=PID_F_angle.Speed_Target-PID_F_angle.Speed;
angle_limitation(&error);
if(error<=DEAD_AREA_F_A && error>=-DEAD_AREA_F_A) //死区
error=0;
P_data=(long)PID_F_angle.P*error;
PID_F_angle.I_count+=error;
if(PID_F_angle.I_count>I_COUNT_MAX) PID_F_angle.I_count=I_COUNT_MAX; //限幅
else if(PID_F_angle.I_count<-I_COUNT_MAX) PID_F_angle.I_count=-I_COUNT_MAX;
// if(error>PID_I_ON || error<-PID_I_ON) //积分分离
// PID_F_angle.I_count=0;
I_data=(long)PID_F_angle.I_count*PID_F_angle.I;
D_data=(long)PID_F_angle.D*(error-PID_F_angle.error_last);
PID_F_angle.error_last=error; //更新误差
Motor_output=P_data+I_data+D_data;
if(Motor_output>w_MAX) Motor_output=w_MAX; //限幅
else if(Motor_output<-w_MAX) Motor_output=-w_MAX;
}
else if(motor==Motor_turn_w) //旋转方向
{
error=PID_T_w.Speed_Target-PID_T_w.Speed;
if(error<=DEAD_AREA_T_W && error>=-DEAD_AREA_T_W) //死区
error=0;
P_data=(long)PID_T_w.P*error;
PID_T_w.I_count+=error;
if(PID_T_w.I_count>I_COUNT_MAX) PID_T_w.I_count=I_COUNT_MAX; //限幅
else if(PID_T_w.I_count<-I_COUNT_MAX) PID_T_w.I_count=-I_COUNT_MAX;
I_data=(long)PID_T_w.I_count*PID_T_w.I;
D_data=(long)PID_T_w.D*(error-PID_T_w.error_last);
PID_T_w.error_last=error; //更新误差
Motor_output=P_data+I_data+D_data;
if(Motor_output>(int)(PWM_DUTY_MAX*battery_protct_ratio)) Motor_output=(int)(PWM_DUTY_MAX*battery_protct_ratio); //限幅
else if(Motor_output<-(int)(PWM_DUTY_MAX*battery_protct_ratio)) Motor_output=-(int)(PWM_DUTY_MAX*battery_protct_ratio);
}
else if(motor==Motor_turn_angle) //旋转方向
{
error=PID_T_angle.Speed_Target-PID_T_angle.Speed;
angle_limitation(&error);
if(error<=DEAD_AREA_T_A && error>=-DEAD_AREA_T_A) //死区
error=0;
P_data=(long)PID_T_angle.P*error;
PID_T_angle.I_count+=error;
if(PID_T_angle.I_count>I_COUNT_MAX) PID_T_angle.I_count=I_COUNT_MAX; //限幅
else if(PID_T_angle.I_count<-I_COUNT_MAX) PID_T_angle.I_count=-I_COUNT_MAX;
I_data=(long)PID_T_angle.I_count*PID_T_angle.I;
if(error-PID_I_OFF) //反向积分分离
PID_T_angle.I_count=0;
D_data=(long)PID_T_angle.D*(error-PID_T_angle.error_last);
PID_T_angle.error_last=error; //更新误差
Motor_output=P_data+I_data+D_data;
if(Motor_output>w_MAX) Motor_output=w_MAX; //限幅
else if(Motor_output<-w_MAX) Motor_output=-w_MAX;
}
else return 0;
// if(motor==LEFT_MOTOR) //左机
// {
// Motor_output=FLP(Motor_output,PID_F.Output_last,Tmp); //低通滤波
// PID_F.Output_last=Motor_output;
// }
// else if(motor==RIGHT_MOTOR) //右机
// {
// Motor_output=FLP(Motor_output,PID_S_w.Output_last,Tmp); //低通滤波
// PID_S_w.Output_last=Motor_output;
// }
Motor_output_final=Motor_output;
return Motor_output_final;
}
//-------------------------------------------------------------------------------------------------------------------
// @brief 舵机PID
// @param motor 选择电机
// @return int16 pwm值
// Sample usage: Turn_PID(CCD_1);
//-------------------------------------------------------------------------------------------------------------------
int16 Turn_PID(Motor_enum motor)
{
int16 Motor_output_final=0,error; //PID调速输出的占空比
long P_data=0,I_data=0,D_data=0,Motor_output=0;
if(motor==Servo_w) //横滚方向
{
error=PID_S_w.Speed_Target-PID_S_w.Speed;
if(error<=DEAD_AREA_S_W && error>=-DEAD_AREA_S_W) //死区
error=0;
P_data=(long)PID_S_w.P*10*error;
PID_S_w.I_count+=error;
if(PID_S_w.I_count>I_COUNT_MAX) PID_S_w.I_count=I_COUNT_MAX; //限幅
else if(PID_S_w.I_count<-I_COUNT_MAX) PID_S_w.I_count=-I_COUNT_MAX;
// if(error>PID_I_ON || error<-PID_I_ON) //积分分离
// PID_F_angle.I_count=0;
I_data=(long)PID_S_w.I_count*10*PID_S_w.I;
D_data=(long)PID_S_w.D*10*(error-PID_S_w.error_last);
PID_S_w.error_last=error; //更新误差
Motor_output=(P_data+D_data+I_data)/100;
if(Motor_output>(SERVO_MAX-SERVO_MID)) Motor_output=(SERVO_MAX-SERVO_MID); //限幅
else if(Motor_output<(SERVO_MID-SERVO_MAX)) Motor_output=(SERVO_MID-SERVO_MAX);
}
else if(motor==Servo_angle) //横滚方向
{
error=PID_S_angle.Speed_Target-PID_S_angle.Speed;
angle_limitation(&error);
if(error<=DEAD_AREA_S_A && error>=-DEAD_AREA_S_A) //死区
error=0;
P_data=(long)PID_S_angle.P*10*error;
PID_S_angle.I_count+=error;
if(PID_S_angle.I_count>I_COUNT_MAX) PID_S_angle.I_count=I_COUNT_MAX; //限幅
else if(PID_S_angle.I_count<-I_COUNT_MAX) PID_S_angle.I_count=-I_COUNT_MAX;
// if(error>PID_I_ON || error<-PID_I_ON) //积分分离
// PID_S_angle.I_count=0;
I_data=(long)PID_S_angle.I_count*10*PID_S_angle.I;
D_data=(long)PID_S_angle.D*10*(error-PID_S_angle.error_last);
PID_S_angle.error_last=error; //更新误差
Motor_output=(P_data+D_data+I_data)/100;
if(Motor_output>w_MAX) Motor_output=w_MAX; //限幅
else if(Motor_output<-w_MAX) Motor_output=-w_MAX;
}
else return SERVO_MID;
// Motor_output=FLP(Motor_output,PID_T_2.Output_last,Tmp); //低通滤波
// PID_T_2.Output_last=Motor_output;
Motor_output_final=Motor_output;
return Motor_output_final;
}
代码和上位机的东西看连接吧,上位机的部分有点乱,代码还是蛮清楚地,里面有一些滤波的东西,是怕不稳定,不过后来发现这款单片机真的太稳定了,写的东西都多余,完全不需要。
链接:https://pan.baidu.com/s/1cJ3BU56pO1ecvjc5rCoWnA?pwd=1111
提取码:1111