当前位置:   article > 正文

蓝牙控制STM32平衡车(一,硬件和程序实现)_以stm32cbt6为核心板的平衡车怎样与手机蓝牙建立联系

以stm32cbt6为核心板的平衡车怎样与手机蓝牙建立联系

一 硬件结构和原理

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

  1. void PWM_Init_TIM1(u16 Psc,u16 Per)//PWM初始化
  2. {
  3. GPIO_InitTypeDef GPIO_InitStruct;
  4. TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
  5. TIM_OCInitTypeDef TIM_OCInitStruct;
  6. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1 | RCC_APB2Periph_AFIO,ENABLE);
  7. GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;
  8. GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8 |GPIO_Pin_11;
  9. GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
  10. GPIO_Init(GPIOA,&GPIO_InitStruct);
  11. TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
  12. TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
  13. TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
  14. TIM_TimeBaseInitStruct.TIM_Period=Per;
  15. TIM_TimeBaseInitStruct.TIM_Prescaler=Psc;
  16. TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStruct);
  17. TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;
  18. TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;
  19. TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;
  20. TIM_OCInitStruct.TIM_Pulse=0;
  21. TIM_OC1Init(TIM1,&TIM_OCInitStruct);
  22. TIM_OC4Init(TIM1,&TIM_OCInitStruct);
  23. TIM_CtrlPWMOutputs(TIM1,ENABLE);
  24. TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable);
  25. TIM_OC4PreloadConfig(TIM1,TIM_OCPreload_Enable);
  26. TIM_ARRPreloadConfig(TIM1,ENABLE);
  27. TIM_Cmd(TIM1,ENABLE);

motor.c

  1. void Motor_Init(void)
  2. {
  3. GPIO_InitTypeDef GPIO_InitStruct;
  4. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  5. GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
  6. GPIO_InitStruct.GPIO_Pin=GPIO_Pin_12 |GPIO_Pin_13 |GPIO_Pin_14 |GPIO_Pin_15;
  7. GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
  8. GPIO_Init(GPIOB,&GPIO_InitStruct);
  9. }
  10. void Load(int moto1,int moto2)
  11. {
  12. if(moto1>0) Ain1=1,Ain2=0;
  13. else Ain1=0,Ain2=1;
  14. TIM_SetCompare1(TIM1,GFP_abs(moto1));
  15. if(moto2>0) Bin1=0,Bin2=1;
  16. else Bin1=1,Bin2=0;
  17. TIM_SetCompare4(TIM1,GFP_abs(moto2));
  18. }

exti.c

  1. void MPU6050_EXTI_Init(void)
  2. {
  3. EXTI_InitTypeDef EXTI_InitStruct;
  4. GPIO_InitTypeDef GPIO_InitStruct;
  5. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE);
  6. GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;/**¡¾1¡¿**///GPIO_Mode_AF_PP
  7. GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
  8. GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
  9. GPIO_Init(GPIOB,&GPIO_InitStruct);
  10. GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource5);//
  11. EXTI_InitStruct.EXTI_Line=EXTI_Line5;
  12. EXTI_InitStruct.EXTI_LineCmd=ENABLE;
  13. EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
  14. EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling;
  15. EXTI_Init(&EXTI_InitStruct);
  16. }

encoder.c

  1. void Encoder_TIM2_Init(void)
  2. {
  3. GPIO_InitTypeDef GPIO_InitStruct;
  4. TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
  5. TIM_ICInitTypeDef TIM_ICInitStruct;
  6. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
  7. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
  8. GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;
  9. GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0 |GPIO_Pin_1;
  10. GPIO_Init(GPIOA,&GPIO_InitStruct);
  11. TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
  12. TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
  13. TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
  14. TIM_TimeBaseInitStruct.TIM_Period=65535;
  15. TIM_TimeBaseInitStruct.TIM_Prescaler=0;
  16. TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
  17. TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
  18. TIM_ICStructInit(&TIM_ICInitStruct);
  19. TIM_ICInitStruct.TIM_ICFilter=10;
  20. TIM_ICInit(TIM2,&TIM_ICInitStruct);
  21. TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
  22. TIM_SetCounter(TIM2,0);
  23. TIM_Cmd(TIM2,ENABLE);
  24. }
  25. void Encoder_TIM4_Init(void)
  26. {
  27. GPIO_InitTypeDef GPIO_InitStruct;
  28. TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
  29. TIM_ICInitTypeDef TIM_ICInitStruct;
  30. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  31. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
  32. GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;
  33. GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6 |GPIO_Pin_7;
  34. GPIO_Init(GPIOB,&GPIO_InitStruct);
  35. TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
  36. TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
  37. TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
  38. TIM_TimeBaseInitStruct.TIM_Period=65535;
  39. TIM_TimeBaseInitStruct.TIM_Prescaler=0;
  40. TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStruct);
  41. TIM_EncoderInterfaceConfig(TIM4,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);
  42. TIM_ICStructInit(&TIM_ICInitStruct);
  43. TIM_ICInitStruct.TIM_ICFilter=10;
  44. TIM_ICInit(TIM4,&TIM_ICInitStruct);
  45. TIM_ClearFlag(TIM4,TIM_FLAG_Update);
  46. TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);
  47. TIM_SetCounter(TIM4,0);
  48. TIM_Cmd(TIM4,ENABLE);
  49. }
  50. int Read_Speed(int TIMx)
  51. {
  52. int value_1;
  53. switch(TIMx)
  54. {
  55. case 2:value_1=(short)TIM_GetCounter(TIM2);TIM_SetCounter(TIM2,0);break;
  56. case 4:value_1=(short)TIM_GetCounter(TIM4);TIM_SetCounter(TIM4,0);break;
  57. default:value_1=0;
  58. }
  59. return value_1;
  60. }

最重要的control.c

  1. #include "control.h"
  2. #include "main.h"
  3. extern Flag_typedef Flag;
  4. float Target_velocity=0;
  5. float Turn_velocity=0;
  6. #define SPEED_Y 500 //前后最大速度
  7. #define SPEED_Z 100//左右最大速度
  8. float Med_Angle=9.5;//机械中值
  9. float
  10. Vertical_Kp=540,//550, 直立环系数
  11. Vertical_Kd=7.5 ;//5.8;
  12. float
  13. Velocity_Kp=-0.021,//0.01, //速度环系数
  14. Velocity_Ki=-0.000105;
  15. float
  16. Turn_Kd=0,//转向环稀释
  17. Turn_Kp=5;
  18. int Vertical_out,Velocity_out,Turn_out=0;
  19. int Vertical(float Med,float Angle,float gyro_Y);
  20. int Velocity(float target ,int encoder_left,int encoder_right);
  21. int Turn(int gyro_Z,int RC);
  22. void EXTI9_5_IRQHandler(void)//整个姿态都是在外部中断进行,PID需要严格的时间,在MPU.C中设置10MS
  23. {
  24. if(EXTI_GetITStatus(EXTI_Line5)!=0)
  25. {
  26. int PWM_out,PWM_dead;
  27. if(PBin(5)==0)
  28. {
  29. EXTI_ClearITPendingBit(EXTI_Line5);
  30. Encoder_Left=-Read_Speed(2);//读取编码器速度,相对安装,其中一个符号取反
  31. Encoder_Right=Read_Speed(4);
  32. mpu_dmp_get_data(&Pitch,&Roll,&Yaw);//获取陀螺仪,加速度和DMP角度
  33. MPU_Get_Gyroscope(&gyrox,&gyroy,&gyroz);
  34. MPU_Get_Accelerometer(&aacx,&aacy,&aacz);
  35. if((Flag.Go==0)&&(Flag.Buck==0))Target_velocity=0;
  36. if(Flag.Go==1)Target_velocity-=2;//蓝牙控制,前进标志被置位,期望速度减小
  37. if(Flag.Buck==1)Target_velocity+=2;
  38. Target_velocity=Target_velocity>SPEED_Y?SPEED_Y:(Target_velocity<-SPEED_Y?(-SPEED_Y):Target_velocity);//限幅
  39. if((Flag.Left==0)&&(Flag.Right==0))Turn_velocity=0;
  40. if(Flag.Left==1)Turn_velocity++;
  41. if(Flag.Right==1)Turn_velocity--;
  42. Turn_velocity=Turn_velocity>SPEED_Z?SPEED_Z:(Turn_velocity<-SPEED_Z?(-SPEED_Z):Turn_velocity);
  43. //数据压入 PID速度环输出给到直立环输入,串级PID
  44. Velocity_out=Velocity(Target_velocity,Encoder_Left,Encoder_Right);
  45. Vertical_out=Vertical(Med_Angle+Velocity_out,Pitch,gyroy);
  46. PWM_out=Vertical_out;
  47. Turn_out=Turn(gyroz,Turn_velocity);
  48. // OLED_Num4(0,0,PWM_out);
  49. if(PWM_out>0){PWM_dead=Dead_value(0);}//死区电压
  50. else if(PWM_out<0){PWM_dead=-Dead_value(0);}
  51. MOTO1=PWM_dead+PWM_out+Turn_out;//PID输出加上死区电压和转向环,最终输出
  52. MOTO2=PWM_dead+PWM_out-Turn_out;
  53. Limit(&MOTO1,&MOTO2);//限幅
  54. Load(MOTO1,MOTO2);//加载到电机
  55. Motor_off(&Med_Angle,&Pitch);//电机异常关闭
  56. }
  57. }
  58. }
  59. int Dead_value(int value)
  60. {
  61. return value;
  62. }
  63. /*************
  64. 直立环,输入机械中值,真实角度,真实角速度
  65. **************/
  66. int Vertical(float Med,float Angle,float gyro_Y)
  67. {
  68. int PWM_out;
  69. PWM_out=Vertical_Kp*(Angle-Med)+Vertical_Kd*(gyro_Y-0);
  70. return PWM_out;
  71. }
  72. /*********************
  73. 速度环,输入期望速度,左右电机速度
  74. *********************/
  75. extern int EnC;
  76. int Velocity(float target ,int encoder_left,int encoder_right)
  77. {
  78. static float Moment;
  79. static int PWM_out,Encoder_Err,Encoder_S,EnC_Err_Lowout,EnC_Err_Lowout_last;
  80. float a=0.6;
  81. Encoder_Err=(encoder_left+encoder_right)-target;
  82. EnC_Err_Lowout=(1-a)*Encoder_Err+a*EnC_Err_Lowout_last;//低通滤波
  83. EnC_Err_Lowout_last=EnC_Err_Lowout;//
  84. Encoder_S+=EnC_Err_Lowout;//误差累积
  85. Encoder_S=Encoder_S;
  86. Encoder_S=Encoder_S>10000?10000:(Encoder_S<(-10000)?(-10000):Encoder_S);//限幅
  87. PWM_out=Velocity_Kp*EnC_Err_Lowout+Velocity_Ki*Encoder_S;//
  88. if(Motor_off(&Med_Angle,&Pitch)==1){Encoder_S=0;}//电机异常关闭积分清零
  89. return PWM_out;
  90. }
  91. /*********************
  92. 转向环,Z轴角速度
  93. *********************/
  94. int Turn(int gyro_Z,int T)
  95. {
  96. int PWM_out;
  97. PWM_out=Turn_Kd*gyro_Z+Turn_Kp*T;
  98. return PWM_out;
  99. }

后续会出PID讲解和调参

链接:https://pan.baidu.com/s/11pMVbMSqpbYqGfJ5TEQGRA 
提取码:1213

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/501063
推荐阅读
相关标签
  

闽ICP备14008679号