一、主要内容
结合阿克曼运动需求,本团队设计了阿克曼运动系统框图如图
主要包括阿克曼小车仿真设计和阿克曼小车实物实现。
图 1 阿克曼运动系统框图
1.1 阿克曼小车仿真设计
本节先介绍阿克曼小车模型,小车仿真设计包括SolidWorks和gazebo三维建模。
1.1.1 阿克曼小车
阿克曼小车是一款经典的车模,小车模型后轮是通过电机直驱,前轮通过舵机控制前轮转向角,前轮部分则是在模型中添加一个竖直的关节,使前轮能围绕此关节转动
1.1.2 SolidWorks三维模型
采用SolidWorks (2016版)设计软件搭建阿克曼小车三维模型如图 2图 3图 4,主体分为底板、电池、控制器、带编码电机的后轮、激光雷达、深度摄像头、阿克曼前轮转向7个部分如图 5。
各部分零件都定义了材料属性,选择取小车底盘中心为模型的原点,配置了小车JRDF文件见附件3。
目前可以导入到gazebo环境中,可以实现小车的前进与后退,但转向无法实现,原因分析由于阿克曼转向结构属于空间四连杆结构 (并联结构不支持)如图 6。
图2正等测
图3前视图
图4俯视图
图5爆炸视图
图6模型导入gazebo效果
1.1.3 gazebo三维建模
为了简化小车的运动,直接调入用阿克曼模型,简化小车的前后轮运动关系,搭配了里程计和摄像头,可以实现小车的转向、直行和后退。
小车分别导入到gazebo和rviz效果分别如图 7图 8建模过程见附件1文档
图 7 加载到gazebo
图8 加载到rvzi
1.2 阿克曼小车实物实现
小车底层搭载STM32F103系列单片机,运动控制算法采用阿克曼算法,解析后分别驱动舵机和编码电机,可以通过审口通讯实现上下位机的人机交互,方便调试我们设计了PS2手柄控制模式。
实现小车的实物制作如图 9。
图9小车实物
1.2.1控制器
小车控制器采用意法半导体STM32F103C6,是一款 ARM 32位 Cortex-M3 微控制器,2MHz 32B 闪存,10KB SRAM,PLL,嵌入式内部 RC 8MHz和 32KHz,实时时钟,嵌套中断控制器,省电式。
JTAG和SWD,2同步,具有输入捕捉、输出比和PWM的16位定时器、16位6通道高级定时器、2个16位看门狗定时器、SysTick定时器、SP112C、2个USART、USB2.0全速接口、CAN2.0B激活、2个12位10通道D转换器。
快速 /0 端口如图 10整体资源满足小车求,10使用情况详细说明,STM32核心板和底板原理图见附件1。
图 10 STM32F103引脚定义图
1.2.2 阿克曼运动算法
阿克曼转向是一种现代汽车的转向方式,在汽车转弯的时候,内外轮转过的角度不一样,内侧轮胎转弯半径小于外侧轮胎。理想的阿克曼转向如图 11,而本车模型采用反向的阿克曼模型。
图 11 理想的阿克曼转向
根据阿克曼转向几何设计转向机构,在车辆沿着弯道转弯时,利用四连杆的相等曲柄,可以使内侧轮的转向角比外侧轮大大约 2~4度,使四个轮子路径的圆心大致上交会于后轴的延长线上瞬时转向中心,从而让车辆可以顺畅的转弯。
阿克曼核心公式如下;
式中:B一汽车前外轮转角,a 一汽车前内轮转角,K一两主销中心距,L一轴距如图 12。
具体实现见附件2中control.c中Kinematic Analysis函数。
图 12 阿克曼数学模型
control.c
#include "control.h"
//#define T 0.245f
//#define L 0.29f
//#define K 14.00f
#define T 0.156f
#define L 0.1445f
#define K 622.8f
u8 Flag_Target,Flag_Change; //相关标志位
//float Voltage_Temp,Voltage_Count,Voltage_All; //电压采样相关变量
int j,sum;
/**************************************************************************
函数功能:小车运动数学模型
入口参数:速度和转角
返回 值:无
**************************************************************************/
void Kinematic_Analysis(float velocity,float angle)
{
Target_A=velocity*(1+T*tan(angle)/2/L);
Target_B=velocity*(1-T*tan(angle)/2/L); //后轮差速
Servo=SERVO_INIT+angle*K; //舵机转向
}
/**************************************************************************
函数功能:所有的控制代码都在这里面
定时中断触发
严格保证采样和数据处理的时间同步
**************************************************************************/
void Control(void)
{
oled_show(); //显示屏打开
Encoder_Left=Read_Encoder(2);
Encoder_Right=-Read_Encoder(3); //读取左右编码器
delay_ms(50); //=====延时等待稳定
if(Turn_Off(Voltage)==0&&Flag_Way==0)
{
jiexi();
Kinematic_Analysis(Velocity,Angle); //小车运动学分析
Motor_A=Target_A*20; //===计算电机A最终PWM
Motor_B=Target_B*20; //===计算电机B最终PWM
Xianfu_Pwm(); //===PWM限幅
Set_Pwm(Motor_A,Motor_B,Servo); //===赋值给PWM寄存器
}
else if(Turn_Off(Voltage)==0&&Flag_Way==1) //===如果不存在异常
{
Get_RC();
Kinematic_Analysis(Velocity,Angle); //小车运动学分析
Motor_A=Incremental_PI_Left(Encoder_Left,Target_A); //===速度闭环控制计算电机A最终PWM
Motor_B=Incremental_PI_Right(Encoder_Right,Target_B); //===速度闭环控制计算电机B最终PWM
Xianfu_Pwm(); //===PWM限幅
Set_Pwm(Motor_A,Motor_B,Servo); //===赋值给PWM寄存器
}
else Set_Pwm(0,0,SERVO_INIT); //===赋值给PWM寄存器
Voltage_Temp=Get_battery_volt(); //=====读取电池电压
Voltage_Count++; //=====平均值计数器
Voltage_All+=Voltage_Temp; //=====多次采样累积
if(Voltage_Count==10) Voltage=Voltage_All/10,Voltage_All=0,Voltage_Count=0;//=====求平均值
if(Flag_Show==0) Led_Flash(100);
else if(Flag_Show==1) Led_Flash(0); //led闪烁
Key(); //===扫描按键状态 单击双击可以改变小车运行状态
}
/**************************************************************************
函数功能:赋值给PWM寄存器
入口参数:PWM
返回 值:无
**************************************************************************/
void Set_Pwm(int motor_a,int motor_b,int servo)
{
if(motor_a<0) PWMA2=7200,PWMA1=7200+motor_a;
else PWMA1=7200,PWMA2=7200-motor_a;
if(motor_b<0) PWMB1=7200,PWMB2=7200+motor_b;
else PWMB2=7200,PWMB1=7200-motor_b;
SERVO=servo;
}
/**************************************************************************
函数功能:限制PWM赋值
入口参数:幅值
返回 值:无
**************************************************************************/
void Xianfu_Pwm(void)
{
int Amplitude=6900; //===PWM满幅是7200 限制在6900
if(Motor_A<-Amplitude) Motor_A=-Amplitude;
if(Motor_A>Amplitude) Motor_A=Amplitude;
if(Motor_B<-Amplitude) Motor_B=-Amplitude;
if(Motor_B>Amplitude) Motor_B=Amplitude;
if(Servo<(SERVO_INIT-500)) Servo=SERVO_INIT-500; //舵机限幅
if(Servo>(SERVO_INIT+500)) Servo=SERVO_INIT+500; //舵机限幅
}
/************************************************************************
函数功能:按键修改小车运行状态
入口参数:无
返回 值:无
**************************************************************************/
void Key(void)
{
u8 tmp,tmp2;
tmp=click();
// tmp=click_N_Double(50); //双击,双击等待时间500ms
if(tmp==1)Flag_Stop=!Flag_Stop;//单击控制小车的启停
//if(tmp==2)Flag_Show=!Flag_Show;//双击控制小车的显示状态
tmp2=Long_Press(); //长按
if(tmp2==1)Flag_Show=!Flag_Show;//控制小车的显示状态
}
/**************************************************************************
函数功能:异常关闭电机
入口参数:电压
返回 值:1:异常 0:正常
**************************************************************************/
u8 Turn_Off( int voltage)
{
u8 temp;
if(voltage<740||Flag_Stop==1)//电池电压低于11.1V关闭电机
{
temp=1;
PWMA1=0; //电机控制位清零
PWMB1=0; //电机控制位清零
PWMA2=0; //电机控制位清零
PWMB2=0; //电机控制位清零
}
else
temp=0;
return temp;
}
/**************************************************************************
函数功能:绝对值函数
入口参数:int
返回 值:unsigned int
**************************************************************************/
int myabs(int a)
{
int temp;
if(a<0) temp=-a;
else temp=a;