• 毕业设计 基于STM32单片机的二轮平衡小车



    0 前言

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。

    为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的是

    🚩 于STM32单片机的二轮平衡小车

    🥇学长这里给一个题目综合评分(每项满分5分)

    • 难度系数:4分
    • 工作量:4分
    • 创新点:3分

    🧿 选题指导, 项目分享:

    https://gitee.com/dancheng-senior/project-sharing-1/blob/master/%E6%AF%95%E8%AE%BE%E6%8C%87%E5%AF%BC/README.md


    1 简介

    今天我们来介绍一下基于STM32单片机的二轮平衡小车模块及算法原理。

    小车可以实现手机APP控制前进、后退、左转和右转。

    利用HC-SR04超声波模块可以实现跟随、避障模式。

    利用智能手机可以实现重力感应控制。

    2 主要器件

    • STM32F103C8T6主控芯片
    • TB6612FNG电机驱动模块
    • MPU6050芯片姿态监测传感器
    • HC-05蓝牙模块
    • HC-SR04超声波模块

    3 实现效果

    在这里插入图片描述
    在这里插入图片描述
    控制板引脚图
    在这里插入图片描述

    4 设计原理

    4.1 PID算法

    PID算法已经有105年的历史了, 它在很多地方都有它的应用,如汽车的定速巡航、3D打印机的温度控制器,自平衡小车,飞行器等。在介绍pid算法之前要先介绍阶响应,阶响应是当输入信号,从零跳变为一之后,系统的输出曲线响应,如下图所示:
    在这里插入图片描述
    图中黑线是输入信号,输入信号从0直接跳变成1之后,红线就是系统的输出响应,可以看到它经过一段时间的上升之后。逐渐稳定到1附近。

    可以通过一些系统性能指标来评价系统阶响应的好坏:

    一. 超调:overshoot

    它是指响应超出稳态值的最大偏移量与稳态值之比

    二. 上升时间:rise time

    是指的响应曲线从10%上升到稳态值90%所需的时间,在上图中就是指rise time。

    三. 稳定时间:seeting time

    就是指响应曲线到达稳定值附近,比如正负-5%左右,并不在超过这一段误差范围之外的最小时间,也就是图中的setting time,到达setting time之后,就可以认为系统基本达到稳定状态了。

    四. 稳态误差:steady-state error

    它也叫静态误差或者静差,它是指的稳态值与设定值之间的误差

    在这里插入图片描述

    如果我们要控制(如上图)无人小车,要让它比小车准确的前进三米,那么它的稳态误差就是它实际行驶的距离与设定值三米之间的差距,上升时间就是指距离值的10%,也就是0.3米上升到2.7米的行驶时间,稳定时间就是指它稳定在三米附近的时间。 我们需要设计一个反馈控制器,目的是使原本性能参数不太好的性能曲线变成理想状态的性能曲线,也就是说它的上升时间和稳定时间都要尽可能小,并且要消除他的稳态值与它的设定值之间的误差。
    在这里插入图片描述
    如上图中系统响应曲线,可以看到它的系统响应时间很长,而且最后的稳态值与它的设定值三米之间还存在很大的误差。

    所以要引入pid控制算法,pid它之所以叫这个名字,是因为它是由p,i,d三种控制器组合而成,p控制器是最基础,在其基础上可以加上i控制器或者d控制器来形成pi或者pd控制器,也可以三种控制器同时使用,也就是最后的pid控制器,下面分别对这三种控制器进行详细介绍。

    P(比例)控制器是最简单的控制器,P英文全称Proportion(比例),也就是比例的意思,比例控制器的基本思想就是对系统的误差呈上一个比例因子kp,从而得到它的控制量输出o,也就是利用误差的正负和大小作出成比例的反应, 比例因子kp,可以得到相应o值的正负和大小,输出量的大小与输入误差信号的大小成比例关系的一种控制。 公式是:O=Kp*e 。

    假设让无人小车前进3m,p控制器输出pwm波的占空比Duty_p,Duty_p =Kp*error,就是Duty_p等于误差乘以kp(比例因子)。加上p控制器之后,系统响应曲线就可以变成下图所示的蓝色曲线。
    在这里插入图片描述

    可以看到p控制器减少了上升时间和稳态误差,但是系统也有本身没有任何超调变成了有一定的超调量,也就是说p控制器增加了系统的超调量。
    在这里插入图片描述
    上图显示的是p控制器中不同的比例因子Kp,kp所对应的三条响应曲线,其中蓝色是p=5的情况,红色是p=10的情况,橙色是p=20的情况,可以看出kp值越大上升时间也就越快,稳态误差越小,但是它的超调量也会随之增大。 通过简单的改变比例系数kp是不能消除系统震荡效果,并且kp值越大,系统震荡的幅度也在增大。也就是通过p控制器对误差所产生的修正会使系统在接近理想状态时产生一个超越稳态值的冲量并产生震荡。要解决震荡,就需要对系统接近理想状态时的动量进行修正。(动量=质量速度) ,也就需要对速度进行修正,使系统在接近理想状态时,为了让他更容易稳定下来,我们要将速度值尽量减小,这就需要用到误差的导数,因为速度的本质就是位移的导数,而这里的误差就是位移值之差,所以速度也可以认为是误差的导数(O=Kd* de/dt),我们需要用它来对输出控制量进行进一步的修正,也就是引入了微分控制。 它可以利用误差的导数,也就是误差的变化率来修正pwm波的占空比,所以现在duty_pd等于误差乘以比例因子kp再加上误差减去上一次误差的变化量乘以一个微分系数kd(Dudy_pd=Kperror+Kd(error-last_error))。它们的响应曲线如下图所示:
    在这里插入图片描述
    蓝线是之前加入p控制器的响应曲线,红线是在pd控制下的响应曲线,可以看出Pd控制器的使用使得超调量相对之前减少了很多,而且稳定时间也有所减少。 其实比例控制器是一种事后调节,它是在发生了误差之后再进行调节,微分控制器是事前进行调节,就是超前预测控制,它在误差有减小趋势的时候,就是这次误差小于上一次误差时,加入d控制器会使得它的输出变小,从而避免过度控制所产生的超调,当误差有增大的趋势时,也就是说这一次的误差大于上次的误差,d控制器会使得输出更大一些,从而抑制误差的增长趋势,加快系统的运动速度使得调节时间减小。 同时也可以发现,此时其实系统还存在稳态误差,为了消除这一点我们需要引入另外一种控制器,也就是积分控制器来改善控制系统,它可以记录自己前面一些时刻的误差,尤其有重复性的,并且值比较固定的误差,特别是稳态误差,它可以将这些误差累加起来,当达到一定阈值,也就是说足够大的时候,系统就会做出一些补偿或者修正。 所以此时Pi控制器的输出也就是pwm波的占空比,(Dudy_pi=Kperror+Ki∑error)Dudy_pi等于误差乘以比例因子kp再加上前一段时间的误差之和乘以积分因子Ki,下图曲线是三种不同控制器下的响应曲线:
    在这里插入图片描述
    绿线和蓝线分别是之前的pd和p控制器所对应的响应曲线,红线是pid控制器所对应的响应曲线,加入了积分控制器之后,系统没有了稳态误差,但是也增加了一定超调量。 所以i积分控制器的主要作用是消除静态误差,使系统最后的稳态值和设定的值之间没有任何误差,同时积分作用的强弱也取决于积分时间常数,如果积分时间常数越大,积分项也会越慢,控制调节效率也就越弱。 综合考虑的所有因素,考虑到误差的变化率以及消除误差的因素,我们可以将这三种控制器全部都用上,也就是同时对误差进行比例积分和微分运算,它的系统响应如下图所示:
    在这里插入图片描述
    此时加入pid闭环控制系统不仅上学时间很快,而且没有任何超调,也没有稳态误差。

    在这里插入图片描述
    简单的做一个总结,pid的主要作用就是设置值和实际值做差,他们的误差,分别输入给比例,积分和微分控制器。比例控制器就是将误差乘以一个常数,积分控制器就是对误差进行一段时间的累积运算,微分就是计算误差的变化率,将这三者的输出之和进行累加,得到最后的pid的输出结果并输出给控制对象,也就是要改进的控制系统,从而形成一个闭环电路。

    4.2 HC-SR04超声波模块

    简介

    HC-SR04超声波模块常用于机器人避障、物体测距、液位检测、公共安防、停车场检测等场所。HC-SR04超声波模块主要是由两个通用的压电陶瓷超声传感器,并加外围信号处理电路构成的。如图:
    在这里插入图片描述
    两个压电陶瓷超声传感器,一个用于发出超声波信号,一个用于接收反射回来的超声波信号。由于发出信号和接收信号都比较微弱,所以需要通过外围信号放大器提高发出信号的功率,和将反射回来信号进行放大,以能更稳定地将信号传输给单片机。模块整体电路如图:

    在这里插入图片描述

    模块参数

    (1)模块主要电气参数

    • 使用电压:DC—5V
    • 静态电流:小于2mA
    • 电平输出:高5V
    • 电平输出:底0V
    • 感应角度:不大于15度
    • 探测距离:2cm-450cm
    • 高精度 可达0.2cm

    (2)模块引脚
    超声波模块有4个引脚,分别为Vcc、 Trig(控制端)、 Echo(接收端)、 GND;其中VCC、GND接上5V电源, Trig(控制端)控制发出的超声波信号,Echo(接收端)接收反射回来的超声波信号。模块如图
    在这里插入图片描述

    4.3 TB6612FNG电机驱动模块

    简介
    TB6612FNG是一款新型驱动器件,能独立双向控制2个直流电机,它具有很高的集成度,同时能提供足够的输出能力,运行性能和能耗方面也具有优势,因此在集成化、小型化的电机控制系统中,它可以作为理想的电机驱动器件。

    原理图如下:
    在这里插入图片描述

    TB6612FNG驱动接口详解

    TB6612FNG驱动如下图所示
    在这里插入图片描述
    接口说明:

    • VM:电机驱动电源输入(2.74-10.8V)
    • VCC:逻辑电平输入(5.0V)
    • AO1和AO2:接直流电机A
    • BO2和BO1:接直流电机B
    • PWMA:控制电机A的转速
    • AIN1和AIN2:控制电机A(停止、正转、反转)
    • STBY:正常工作/待机状态控制端PWMB:控制电机B的转速
    • BIN1和BIN2:控制电机B(停止、正转、反转)
    • GND:接地

    AIN1和AIN2用于控制电机A停止、正转、反转,真值表如下
    在这里插入图片描述

    AIN1001AIN2010状态停止正转反转BIN1和BIN2的真值表同上

    4.4 MPU6050芯片姿态监测传感器

    MPU6050

    MPU-60x0是全球首例9轴运动处理传感器。它集成了3轴MEMS陀螺仪,3轴MEMS加速度计,以及一个可扩展的数字运动处理器DMP(Digital Motion Processor),可用I2C接口连接一个第三方的数字传感器,比如磁力计。扩展之后就可以通过其I2C或SPI接口输出一个9轴的信号(SPI接口仅在MPU-6000可用)。MPU-60x0也可以通过其I2C接口连接非惯性的数字传感器,比如压力传感器。

    在这里插入图片描述

    MPU-60x0对陀螺仪和加速度计分别用了三个16位的ADC,将其测量的模拟量转化为可输出的数字量。为了精确跟踪快速和慢速的运动,传感器的测量范围都是用户可控的,陀螺仪可测范围为±250,±500,±1000,±2000°/秒(dps),加速度计可测范围为±2,±4,±8,±16g。一个片上1024字节的FIFO,有助于降低系统功耗。和所有设备寄存器之间的通信采用400kHz的I2C接口或1MHz 的SPI接口(SPI仅MPU-6000可用)。对于需要高速传输的应用,对寄存器的读取和中断可用20MHz的SPI。另外,片上还内嵌了一个温度传感器和在工作环境下仅有±1%变动的振荡器。

    5 部分核心代码

    /************************** .h文件******************************/
    
    
    #ifndef _pid_H
    #define _pid_H
    
    #define     MODEL_P       1
    #define     MODEL_PI      2
    #define     MODEL_PID     3
    
    typedef struct
    {
        u8 choose_model;    		//使用哪个模式调节,以方便分布调试
        
        float curr;                   //当前值
        float set;                    //设定值
        
        float En;                     //当前时刻误差值
        float En_1;                   //前一时刻误差值
        float En_2;                   //前二时刻误差值
            
        float Kp;                     //比例系数
        float T;                      //每隔T控制器输出一次PID运算结果
    
        u16   Tdata;                  //判断PID周期到没到
        float Ti;                     //积分时间常数
        float Td;                     //微分时间常数
        
        float Dout;                   //增量PID计算本次应该输出的增量值-本次计算的结果
    
        float OUT0;                   //一个维持的输出,防止失控
        
        short currpwm;                //当前的pwm宽度
        u16 pwmcycle;                 //pwm周期
    
    }PID;
    
    extern u8 STATUS;
    extern PID pid;
    void PID_Init(void);      //增量式PID初始化
    void pid_calc(void);              //pid计算 并输出
    
    
    /************************** .c文件******************************/
    
    
    #endif
    #include "pid.h"
    #include "PWM_Config.h"
    #include "USART_Config.h"   		//USART设置
    
    PID pid;
    
    void PID_Init()  		//
    {
        pid.choose_model = MODEL_PID;
        pid.T=330;                		//采样周期,定时器使用1ms,则最小执行PID的周期为330ms
        
        pid.set =280;            		//用户设定值
        pid.Kp=0.5;                	//比例系数
        pid.Ti=40;               		//微分系数常数
        pid.Td=10;                		//积分时间常数
        pid.OUT0=0;                	//一个维持的输出
    
        pid.pwmcycle = 330;    		//PWM的周期
    }
        
    void pid_calc()  			
    {
     	float dk1;float dk2;
      	float t1,t2,t3;
        
        if(pid.Tdata < (pid.T))  		//最小计算周期未到
         {
                return ;
         }
        pid.Tdata = 0;
        
        pid.En=pid.set-pid.curr;  		//本次误差
        dk1=pid.En-pid.En_1;   		//本次偏差与上次偏差之差
        dk2=pid.En-2*pid.En_1+pid.En_2;
        
      	t1=pid.Kp*dk1;                   	//比例
        
        t2=(pid.Kp*pid.T)/pid.Ti;      	//积分
        t2=t2*pid.En;
        
        t3=(pid.Kp*pid.Td)/pid.T;        	//微分
        t3=t3*dk2;
        
        switch(pid.choose_model)
         {
             case MODEL_P:    		//仅用P
    				pid.Dout= t1;                    
    				printf("使用P运算\r\n") ;
                 	break;
             
             case MODEL_PI:  		//仅用PI
    				pid.Dout= t1+t2;                
    				printf("使用PI运算\r\n") ;
                	break;
                     
             case MODEL_PID: 		//用PID
    				pid.Dout= t1+t2+t3;        
    				printf("使用PID运算\r\n") ;
    				break;
    	 } 
              
        pid.currpwm+=pid.Dout;  		//本次应该输出的PWM
        printf("PID算得的OUT:\t%d\r\n",(int)pid.currpwm) ;
         
        if(pid.currpwm>pid.pwmcycle)       //确保值在0-pid.pwmcycle之间
        {
          pid.currpwm=pid.pwmcycle;
        }
        if(pid.currpwm<0)
        {
         pid.currpwm=0;
        }
        
        printf("实际输出使用的OUT:\t%d\r\n",(int)pid.currpwm) ;
        pid.En_2=pid.En_1;
        pid.En_1=pid.En;    
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    void tim4_pwm_init(u16 arr,u16 psc)
    {  
    	
    	RCC->APB1ENR |= 1<<2;    //时钟复位TIM4
    	TIM4->CR1=0x0080;     //ARPE 使能,Timx_ARR寄存器被装入缓冲器
    	TIM4->CR1|=0x01;      //使能定时器 4
      
    	  RCC->APB2ENR |= 1<<3;       //时钟使能GPIOB  由电路图知GPIO6和TIM4的通道一复用 
    	  GPIOB->CRL&=0X00F00000;
    	  GPIOB->CRL|=0XBB033333;    //pb6  7复用功能输出   PB6输出ch1,pb7输出ch2
    	  TIM4->ARR = arr;            //设置计数器自动重装载值
    	  TIM4->PSC = psc;              //预分频器设计
    	
    	  TIM4->CCMR1 =0X6060;  //配置Tim4输入捕获模式
    	
    	  TIM4->CCMR1|=1<<3;            //CH1 预装载使能   
    	  TIM4->CCMR1|=1<<11;           //CH 2 预装载使能
    	
    	  TIM4->CCER |=1<<0;            //OC1 输出使能 
    	  TIM4->CCER |=1<<4;            //OC2 输出使能
    	
    	  
    }
    这里用到的寄存器全部可以在stm32中文手册里查到。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    5 最后

  • 相关阅读:
    SpringBoot项目统一处理返回值和异常
    分布式秒杀方案--java
    Java 终极学习路线 - 共计 9 大模块 /6 大框架 /13 个中间件
    数字人惯性动作捕捉技术服务,激发吉祥物IP创新活力
    <C++> 反向迭代器
    C++ 即将超越 Java,TIOBE 6 月编程语言排行榜发布!
    云存储服务OneDrive捆绑系统销售,30多家欧洲公司投诉微软垄断
    防御DDOS的方法是什么?
    opengl动态修改vbo数据
    IPv4/IPv6、DHCP、网关、路由
  • 原文地址:https://blog.csdn.net/m0_71572576/article/details/127238409