赞
踩
1.器件选型
主控芯片是F1系列的MCU
电机的话最好是那种精度较高的编码电机,当然淘宝那种霍尔的编码电机也是可以实现的;
电机驱动的话推荐TB6612驱动,带两个电机没有问题,并且体积比较小,可以直接画在PCB上;因为我手头有一个L298N的驱动,所以我采用的是L298N,当然驱动程序是可以通用的,注意好接线就行。
陀螺仪的话采用MPU6050完全满足,并且市面上资料比较多,用正点原子的DMP姿态解算非常方便;
蓝牙用的是HC-06的蓝牙模块,淘宝就有,当然HC-05主从一体的也可以;
显示屏使用0.96OLED就可以;
稳压的话可以直接淘宝买LM2595稳压就能满足,使用的时候要用电压表打下输出电压,刚买回来的输出电压一般不是5V;
电源建议买12V的锂电池组,不建议三个电池带一个电池盒,电池比较容易挂掉。
至于其他零件都比较随意了一般实验室都有,车模的话讲究重心低,结构紧凑最好,有利于后期的调参(减少痛苦)
2.实现原理
单片机通过获取陀螺仪的角度等数据判断当前姿态,另外编码电机提供的计数值,综合通过PID算法来控制PWM的输出作用到电机上,(即直立环和速度环)最终的控制都是加载到电机上,其中逻辑控制电机的正反转,PWM控制转速;当倾角越大转速越快,具体的控制效果在下章PID调参中具体讲解。
另外就是其他外设,显示屏显示俯仰角或其他数据用于调试。通过蓝牙手机和单片机通信,控制车的前后左右。
3.硬件原理
(1)编码电机
编码电机有光电和霍尔的,其中的计数原理略有不同,但是最终实现的目的都是一样的,具体的原理可以网上了解。编码电机码盘上一般有6个接口,分别为M- VCC OA OB GND M+(不同的电机顺序可能不一样,不过市面上的直流减速编码电机都是这样),其中最外层的两个是电机的正负极,正接就是正转,反接反转,M+,M-接在电机驱动的输出上。VCC和GND是编码盘的5V供电,最内层的OA OB是编码电机的信号线,具体原理可以了解AB相正交解码,会用就行。
只要电机轮子转动,编码器的计数就会增大或者减小,通过AB相将信号送给单片机,此时就能获取电机转动的数据。
(2)电机驱动
先贴张L298N的引脚图
L298N可以带两个电机,左右马达输出就接在电机的正负极上,12V电源供电可以直接从电池引入,GND就没什么了;再向下以此为ENA IN1 IN2 IN3 IN4 ENB,我们使用时将ENA和ENB引脚的跳线帽拿掉,前三个是通道A的,同理后面三个是通道B的引脚,分别控制两个电机;ENA和ENB接单片机的PWM输出信号(控制电机的转速),IN1和IN2接从单片机输出的逻辑信号(控制电机的正反转),同理的IN3,IN4,ENB也是一样;
通过IN引脚获得逻辑输入,EN引脚获得PWM信号最终改变马达输出端的电压以及电压的方向,从而实现电机的控制。
(3)陀螺仪
MPU6050陀螺仪内部带有三轴陀螺仪和三轴加速度器,和DMP数字运动处理器,有了陀螺仪和加速度数据就可以解算出欧拉角,俯仰角,横滚角,偏航角;根据DMP解算大大减轻了MCU的负担,我们就可以不太关心内部具体如何解算,根据函数调用直接获取数据便可;有想具体了解内部如何工作的可以查阅资料;引脚的话一般会有8个分别为VCC GND SCL SDA XDA XCL AD0 INT,其中我们用到的VCC和GND肯定要的(注意电源供电3.3V直接接5V可能会烧掉)。SCL和SDA是通信引脚,协议是IIC通信和后面的OLED一样,还有一个就是最后一个INT引脚,接在单片机的外部中断引脚上,DMP解算完成触发外部中断,在中断里更新数据信息。
(4)显示
显示的话使用OLED显示屏调试也是可以的,在上面显示小车的俯仰角,另外也可以用串口打印显示在电脑上也是可以的。OLED显示屏的电源可以接5V也可以3.3V,理论是要接3.3V 不过我的接的5V也可以使用。SCL和SDA同样是数据信号引脚,用的软件IIC。
(5)蓝牙
蓝牙用的是HC06模块,我的这个只能是从机模式被动连接,有HC05的主从一体更好。蓝牙的RXD和TXD接在单片机的USART3的引脚上,蓝牙和手机连接,至于蓝牙的设置可以网上搜索AT指令,蛮简单的,注意HC05和HC06的AT指令的格式是不一样的,将蓝牙配置为从机模式,设置名称,密码还有波特率(HC06默认9600,不调也可以)
蓝牙和手机连接成功之后就相当与信号线,作为信号传输。
下面就贴几个重要的代码
pwm.c
- void PWM_Init_TIM1(u16 Psc,u16 Per)//PWM初始化
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
- TIM_OCInitTypeDef TIM_OCInitStruct;
-
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1 | RCC_APB2Periph_AFIO,ENABLE);
-
- GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
- GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8 |GPIO_Pin_11;
- GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
- GPIO_Init(GPIOA,&GPIO_InitStruct);
-
- TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
- TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
- TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
- TIM_TimeBaseInitStruct.TIM_Period=Per;
- TIM_TimeBaseInitStruct.TIM_Prescaler=Psc;
- TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStruct);
-
- TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;
- TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;
- TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;
- TIM_OCInitStruct.TIM_Pulse=0;
- TIM_OC1Init(TIM1,&TIM_OCInitStruct);
-
- TIM_OC4Init(TIM1,&TIM_OCInitStruct);
-
- TIM_CtrlPWMOutputs(TIM1,ENABLE);
-
- TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable);
- TIM_OC4PreloadConfig(TIM1,TIM_OCPreload_Enable);
- TIM_ARRPreloadConfig(TIM1,ENABLE);
-
- TIM_Cmd(TIM1,ENABLE);
motor.c
- void Motor_Init(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
- GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
- GPIO_InitStruct.GPIO_Pin=GPIO_Pin_12 |GPIO_Pin_13 |GPIO_Pin_14 |GPIO_Pin_15;
- GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
- GPIO_Init(GPIOB,&GPIO_InitStruct);
- }
-
-
- void Load(int moto1,int moto2)
- {
-
- if(moto1>0) Ain1=1,Ain2=0;
- else Ain1=0,Ain2=1;
- TIM_SetCompare1(TIM1,GFP_abs(moto1));
- if(moto2>0) Bin1=0,Bin2=1;
- else Bin1=1,Bin2=0;
- TIM_SetCompare4(TIM1,GFP_abs(moto2));
- }
exti.c
- void MPU6050_EXTI_Init(void)
- {
- EXTI_InitTypeDef EXTI_InitStruct;
- GPIO_InitTypeDef GPIO_InitStruct;
-
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE);
-
- GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;/**¡¾1¡¿**///GPIO_Mode_AF_PP
- GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
- GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
- GPIO_Init(GPIOB,&GPIO_InitStruct);
-
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource5);//
-
- EXTI_InitStruct.EXTI_Line=EXTI_Line5;
- EXTI_InitStruct.EXTI_LineCmd=ENABLE;
- EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
- EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling;
- EXTI_Init(&EXTI_InitStruct);
- }
-
encoder.c
-
- void Encoder_TIM2_Init(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
- TIM_ICInitTypeDef TIM_ICInitStruct;
-
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
-
- GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;
- GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0 |GPIO_Pin_1;
- GPIO_Init(GPIOA,&GPIO_InitStruct);
-
- TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
- TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
- TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
- TIM_TimeBaseInitStruct.TIM_Period=65535;
- TIM_TimeBaseInitStruct.TIM_Prescaler=0;
- TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
-
- TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
-
- TIM_ICStructInit(&TIM_ICInitStruct);
- TIM_ICInitStruct.TIM_ICFilter=10;
- TIM_ICInit(TIM2,&TIM_ICInitStruct);
-
- TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
-
- TIM_SetCounter(TIM2,0);
-
- TIM_Cmd(TIM2,ENABLE);
- }
-
-
- void Encoder_TIM4_Init(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
- TIM_ICInitTypeDef TIM_ICInitStruct;
-
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
-
- GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;
- GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6 |GPIO_Pin_7;
- GPIO_Init(GPIOB,&GPIO_InitStruct);
-
- TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
- TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
- TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
- TIM_TimeBaseInitStruct.TIM_Period=65535;
- TIM_TimeBaseInitStruct.TIM_Prescaler=0;
- TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStruct);
-
- TIM_EncoderInterfaceConfig(TIM4,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
-
- TIM_ICStructInit(&TIM_ICInitStruct);
- TIM_ICInitStruct.TIM_ICFilter=10;
- TIM_ICInit(TIM4,&TIM_ICInitStruct);
-
- TIM_ClearFlag(TIM4,TIM_FLAG_Update);
- TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);
-
- TIM_SetCounter(TIM4,0);
-
- TIM_Cmd(TIM4,ENABLE);
- }
-
-
- int Read_Speed(int TIMx)
- {
- int value_1;
- switch(TIMx)
- {
- case 2:value_1=(short)TIM_GetCounter(TIM2);TIM_SetCounter(TIM2,0);break;
- case 4:value_1=(short)TIM_GetCounter(TIM4);TIM_SetCounter(TIM4,0);break;
- default:value_1=0;
- }
- return value_1;
- }
最重要的control.c
- #include "control.h"
- #include "main.h"
- extern Flag_typedef Flag;
- float Target_velocity=0;
- float Turn_velocity=0;
-
- #define SPEED_Y 500 //前后最大速度
- #define SPEED_Z 100//左右最大速度
- float Med_Angle=9.5;//机械中值
- float
- Vertical_Kp=540,//550, 直立环系数
- Vertical_Kd=7.5 ;//5.8;
- float
- Velocity_Kp=-0.021,//0.01, //速度环系数
- Velocity_Ki=-0.000105;
- float
- Turn_Kd=0,//转向环稀释
- Turn_Kp=5;
-
- int Vertical_out,Velocity_out,Turn_out=0;
-
- int Vertical(float Med,float Angle,float gyro_Y);
- int Velocity(float target ,int encoder_left,int encoder_right);
- int Turn(int gyro_Z,int RC);
-
- void EXTI9_5_IRQHandler(void)//整个姿态都是在外部中断进行,PID需要严格的时间,在MPU.C中设置10MS
- {
- if(EXTI_GetITStatus(EXTI_Line5)!=0)
- {
- int PWM_out,PWM_dead;
- if(PBin(5)==0)
- {
- EXTI_ClearITPendingBit(EXTI_Line5);
-
-
- Encoder_Left=-Read_Speed(2);//读取编码器速度,相对安装,其中一个符号取反
- Encoder_Right=Read_Speed(4);
- mpu_dmp_get_data(&Pitch,&Roll,&Yaw);//获取陀螺仪,加速度和DMP角度
- MPU_Get_Gyroscope(&gyrox,&gyroy,&gyroz);
- MPU_Get_Accelerometer(&aacx,&aacy,&aacz);
-
- if((Flag.Go==0)&&(Flag.Buck==0))Target_velocity=0;
- if(Flag.Go==1)Target_velocity-=2;//蓝牙控制,前进标志被置位,期望速度减小
- if(Flag.Buck==1)Target_velocity+=2;
- Target_velocity=Target_velocity>SPEED_Y?SPEED_Y:(Target_velocity<-SPEED_Y?(-SPEED_Y):Target_velocity);//限幅
- if((Flag.Left==0)&&(Flag.Right==0))Turn_velocity=0;
- if(Flag.Left==1)Turn_velocity++;
- if(Flag.Right==1)Turn_velocity--;
- Turn_velocity=Turn_velocity>SPEED_Z?SPEED_Z:(Turn_velocity<-SPEED_Z?(-SPEED_Z):Turn_velocity);
-
-
- //数据压入 PID速度环输出给到直立环输入,串级PID
- Velocity_out=Velocity(Target_velocity,Encoder_Left,Encoder_Right);
- Vertical_out=Vertical(Med_Angle+Velocity_out,Pitch,gyroy);
- PWM_out=Vertical_out;
-
- Turn_out=Turn(gyroz,Turn_velocity);
-
- // OLED_Num4(0,0,PWM_out);
- if(PWM_out>0){PWM_dead=Dead_value(0);}//死区电压
- else if(PWM_out<0){PWM_dead=-Dead_value(0);}
- MOTO1=PWM_dead+PWM_out+Turn_out;//PID输出加上死区电压和转向环,最终输出
- MOTO2=PWM_dead+PWM_out-Turn_out;
-
- Limit(&MOTO1,&MOTO2);//限幅
- Load(MOTO1,MOTO2);//加载到电机
- Motor_off(&Med_Angle,&Pitch);//电机异常关闭
-
- }
- }
- }
-
-
- int Dead_value(int value)
- {
- return value;
- }
- /*************
- 直立环,输入机械中值,真实角度,真实角速度
- **************/
- int Vertical(float Med,float Angle,float gyro_Y)
- {
- int PWM_out;
- PWM_out=Vertical_Kp*(Angle-Med)+Vertical_Kd*(gyro_Y-0);
- return PWM_out;
- }
-
-
-
- /*********************
- 速度环,输入期望速度,左右电机速度
- *********************/
- extern int EnC;
- int Velocity(float target ,int encoder_left,int encoder_right)
- {
- static float Moment;
- static int PWM_out,Encoder_Err,Encoder_S,EnC_Err_Lowout,EnC_Err_Lowout_last;
- float a=0.6;
-
-
- Encoder_Err=(encoder_left+encoder_right)-target;
- EnC_Err_Lowout=(1-a)*Encoder_Err+a*EnC_Err_Lowout_last;//低通滤波
- EnC_Err_Lowout_last=EnC_Err_Lowout;//
-
-
- Encoder_S+=EnC_Err_Lowout;//误差累积
- Encoder_S=Encoder_S;
-
-
- Encoder_S=Encoder_S>10000?10000:(Encoder_S<(-10000)?(-10000):Encoder_S);//限幅
- PWM_out=Velocity_Kp*EnC_Err_Lowout+Velocity_Ki*Encoder_S;//
- if(Motor_off(&Med_Angle,&Pitch)==1){Encoder_S=0;}//电机异常关闭积分清零
- return PWM_out;
- }
-
-
-
- /*********************
- 转向环,Z轴角速度
- *********************/
- int Turn(int gyro_Z,int T)
- {
- int PWM_out;
-
- PWM_out=Turn_Kd*gyro_Z+Turn_Kp*T;
- return PWM_out;
- }
后续会出PID讲解和调参
链接:https://pan.baidu.com/s/11pMVbMSqpbYqGfJ5TEQGRA
提取码:1213
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。