一 硬件结构和原理

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_PPGPIO_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;//机械中值
floatVertical_Kp=540,//550,    直立环系数Vertical_Kd=7.5 ;//5.8;
floatVelocity_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速度环输出给到直立环输入,串级PIDVelocity_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

蓝牙控制STM32平衡车(一,硬件和程序实现)相关推荐

  1. 自制stm32平衡车

    自制stm32平衡车 2019-06-20 19:34:53 HES_C 阅读数 259更多 马上高考了,没事写一篇制作平衡车的教程. 先来一个段子: 对于7,8号的高考,我们早就表明态度:不愿考,但 ...

  2. 毕业设计 STM32平衡车设计与实现

    文章目录 1 简介 1 课题描述 2 课题设计内容 3 平衡车控制原理 4 关键算法 4.1 PID控制算法 4.2 卡尔曼滤波 5 硬件设计 5.1 stm32部分 5.2 电机驱动电路设计 5.3 ...

  3. 三天让车立起来!STM32平衡车入门PID —— 第二天(软件算法)

    说明:本文章适用于STM32初学者,想完成一个好玩且有深度的项目但不知道从何下手的同学. 平衡车是我入门STM32的第一个实战项目,前前后后和我搭硬件的队友路总(硬件大佬,专注于PCB画板)搭了有七八 ...

  4. 小白入门STM32(1)----手机蓝牙控制STM32单片机点亮LED

    文章目录 引言导读 一.通信基础知识 1.1 通信到底传输的是什么? 1.2 比特率和波特率 习题 1.1 双工和单工 习题 1.2 串行和并行 1.3 异步通信和同步通信 习题 二.连接STM32单 ...

  5. STM32平衡车之陀螺仪MPU6050

    关于MPU6050前言简介 首先,个人是通过野火的视频,有专门介绍MPU6050的版块来做的了解. 然后关于MPU6050 基本认识跟坐标系就不做阐述了 MPU6050主要是陀螺仪跟加速度计" ...

  6. 独轮平衡车c语言源码,stm32平衡车源码

    #include "sys.h" /************************************************************************ ...

  7. 基于RT-THREAD nano的平衡车--硬件

    简要 平衡车DIY是我一个2019年初的DIY作品,那时候只完成了硬件开发和平衡的算法,为了不留遗憾,所以重新完善它. 文章分为4篇进行说明: <平衡车 - 硬件>:讲解平衡车的硬件设计. ...

  8. 基于RT-THREAD nano的平衡车--上位机软件

    简要 平衡车文章分为4篇进行说明: <平衡车 - 硬件>:讲解平衡车的硬件设计. <平衡车 - 软件>:讲解平衡车的软件设计,算法. <平衡车 - 上位机>:讲解调 ...

  9. (置顶)飞控板不用看得高大上,本质就是STM32加那几个传感器,和平衡车板子差不多,是完全可以自己画的,甚至不用画,买个STM32核心板+十轴模块

    你自己画一块板子,然后真正飞成了,我觉得你会非常开心的,这种感觉不一样的我觉得,你说是不是. 飞控我们不用看得高大上,本质还是STM32加传感器,可能和平衡车的差不多,所以不用觉得高大上什么的. 真的 ...

最新文章

  1. Vertica的这些事lt;十二gt;—— vertica存储统计信息
  2. 什么是ECS以及如何使用登陆
  3. 前端(jQuery)(5)-- jQuery AJAX异步访问和加载片段
  4. How to Make a Computer Operating System
  5. php -i | grep configure,PHP7中I/O模型内核剖析详解
  6. python面试题_面试时全对这25道python面试题,成就了我月薪25K!附教程分享)
  7. MyBatis关键配置-接口注入使用
  8. 幻像类型提高了编译时的安全性
  9. 使用Mybatis Generator结合Ant脚本快速自动生成Model、Mapper等文件的方法
  10. 大数据分析必须要会的数据预处理操作(一)!!!
  11. 关于nginx keep-alive 参数的验证和心得
  12. node.js的初步见解
  13. 靠,竟然有如此沙雕的代码注释!
  14. 傲腾加速内存安装调试
  15. MovieLens数据集
  16. android官网m魅族15,魅族15/Plus/Lite等机型现身Android官网:设计惊艳
  17. Web Vue VIII
  18. Android 用官方SDK实现第三方(qq、微信、微博等)分享和登录
  19. MountVolume.NewMounter initialization failed for volume “pvc-61dedc85-ea5a-4ac7-aaf3-e072e2e46e18“
  20. vcruntime140.dll不可用或缺少

热门文章

  1. 软件测试工程师面试题
  2. 浏览器通过f12来限制网速
  3. Unix C学习之文件夹操作
  4. 软件定义汽车时代下,传统汽车制造产业如何破局?
  5. 中小企业开展网络营销的四个关键步骤
  6. 数据库ACID的含义
  7. python3爬虫系列03之requests库:根据关键词自动爬取下载百度图片
  8. 树莓派的有线网络和无线网络设置
  9. 官宣!华为断臂为了自救
  10. ntp服务器配置详解