前言
近年来,移动机器人是目前科学领域比较活跃的领域之一,其应用范围越来越广泛,面临的环境也越来越复杂,这就要求机器人能够适应一些复杂的环境和任务。二轮自平衡机器人正是在这一背景下提出来的,对于制作此种类型的自平衡小车无疑对我stm32的学习有莫大的好处,一方面深入了解stm32以及学习串级PID的运用,另一方面也是对我近期学习的硬件方面知识一个检验。
本人为32小白水平有限,同时也是第一篇博客,如果有地方描述不清晰不正确,欢迎大佬指出一起讨论
原理介绍
-
参考资料
-
系统框图以及运动分析
系统框图
获取小车的速度和角度是实现小车自平衡的前提,数据在stm32的中断控制中配合PID算法使用,输出数值给PWM寄存器控制相应电机,从而实现平衡。遥控部分是控制转向,根据移动终端向蓝牙发送数据从而实现相应的运动。
我们可以将平衡小车的运动方式类比为手指上放置一根筷子,保持其直立状态,根据生活经验可知,当筷子向某个方向倒下时,我们眼睛观察到筷子倒下反馈给大脑,大脑控制手迅速向筷子倒下的方向运动,使其始终保持原有直立状态。同样,平衡小车道理相同,当传感器检测到小车有倒下的趋势时,控制轮子向相同方向运动,不同角度会输出不同速度即可保持平衡。
运动分析
平衡小车的控制可以分为直立控制,速度控制以及转向控制,即直立环、速度环和转向环,使小车能够保持平衡状态的是速度环以及直立环,通过传感器读取角度输出不同的PWM即不同的速度使其保持平衡。其中单独的直立环可以使小车保持直立,但如果受到外力的影响,那么不用多想,小车一定会倒下,将直立环和速度环串起来,那么当小车受到外力时,也能迅速平衡。
运动控制
小车的直立控制是通过负反馈控制的

高中时期大家对正反馈和负反馈都有所了解,这里不过多解释。直立控制实际上就是将小车控制在一定角度内,这个角度由小车的机械零点有关(你搭建的小车重心),对于机械零点的确定会在调试中说明,上文提到如果只有直立环不足以小车完全保持平衡,所以我们还需要加入速度环。
小车的速度控制是通过正反馈控制的

我们可以假设此时小车处于直立状态,如果小车想向前运动,那么小车必然要向前倾斜获取向前的加速度,但是向前倾斜轮子必然要向后转动,那么小车的速度会下降(轮子向后转动了)假如为负反馈那么小车必然会增加倾角,一直循环下去这样则会加速小车的倒下,所以在速度控制中反馈系统应该是正反馈。
转向控制
所谓转向环即当小车已经能保证基本的平衡时,通过蓝牙控制使小车改变方向的控制系统
- 定时器PWM输出以及编码器模式
PWM输出
脉冲宽度调制(PWM),是英文“Pulse Width Modulation” 的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。
当小车在运行过程中随着倾角的改变小车的速度也应该发生变化,如果只是单纯给予高低电平,只能以额定速度运动,显然不能达到我们想达到的程度,所以我们需要定时器输出PWM波,我们选用的主控模块只有4个定时器(1个高级3个通用)一个定时器有4个通道控制小车显然是足够的。当然如果你选择其他系列芯片会有多个定时器,选择也会更多
(注意基本定时器无法输出PWM脉冲)
其中如果用到高级定时器TIM1以及TIM8注意除了基础的配置还需要使能刹车和死区寄存器,以使能整个PWM输出,还需要使能高级定时器特有的寄存器。
TIM_CtrlPWMOutputs(TIM1,ENABLE); //高级定时器特有
编码器模式
上文系统框图可以看出,速度系统需要一个速度反馈,其他系统的反馈可以通过6050读取,那么此时小车的速度如何读取呢?这里就需要编码器读取此时的速度。编码器会引出AB相,通过STM32的编码器模式以特定的GPIO口连接编码器AB相,读取脉冲,从而读取速度。
通过查阅参考手册不难得知只有高级定时器以及通用定时器具有检测编码器的功能,基本定时器并不具备这种功能。所以我们可以配置定时器来读取编码器数据。
这里需要注意的是:编码器模式只有定时器的通道一和通道二可以使用,即只有TIMx_CH1 TIMx_CH2 可以使用
千万注意这一点,如果自己打板的话没注意这一点就可以重新打板了 /(ㄒoㄒ)/
参考手册如下:
选择编码器接口模式的方法是:如果计数器只在TI2的边沿计数,则置TIMx_SMCR寄存器中的SMS=001;如果只在TI1边沿计数,则置SMS=010;如果计数器同时在TI1和TI2边沿计数,则置SMS=011。
通过设置TIMx_CCER寄存器中的CC1P和CC2P位,可以选择TI1和TI2极性;如果需要,还可以对输入滤波器编程。
两个输入TI1和TI2被用来作为增量编码器的接口。参看下表:
假定计数器已经启动(TIMx_CR1寄存器中的CEN=’1’),计数器由每次在TI1FP1或TI2FP2上的有效跳变驱动。 TI1FP1和TI2FP2是TI1和TI2在通过输入滤波器和极性控制后的信号;如果没有滤波和变相,则TI1FP1=TI1,TI2FP2=TI2。根据两个输入信号的跳变顺序,产生了计数脉冲和方向信号。依据两个输入信号的跳变顺序,计数器向上或向下计数,同时硬件对TIMx_CR1寄存器的DIR位进行相应的设置。不管计数器是依靠TI1计数、依靠TI2计数或者同时依靠TI1和TI2计数。在任一输入端(TI1或者TI2)的跳变都会重新计算DIR位。
编码器接口模式基本上相当于使用了一个带有方向选择的外部时钟。这意味着计数器只在0到TIMx_ARR寄存器的自动装载值之间连续计数(根据方向,或是0到ARR计数,或是ARR到0计数)。所以在开始计数之前必须配置TIMx_ARR;同样,捕获器、比较器、预分频器、触发输出特性等仍工作如常。在这个模式下,计数器依照增量编码器的速度和方向被自动的修改,因此计数器的内容始终指示着编码器的位置。计数方向与相连的传感器旋转的方向对应。
当定时器配置成编码器接口模式时,提供传感器当前位置的信息。使用第二个配置在捕获模式的定时器,可以测量两个编码器事件的间隔,获得动态的信息(速度,加速度,减速度)。指示机械零点的编码器输出可被用做此目的。根据两个事件间的间隔,可以按照固定的时间读出计数器。如果可能的话,你可以把计数器的值锁存到第三个输入捕获寄存器(捕获信号必须是周期的并且可以由另一个定时器产生);也可以通过一个由实时时钟产生的DMA请求来读取它的值。
MPU-6050姿态解算
-
六轴传感器读取角度
想要小车直立起来由上文可知我们需要读取y轴的倾斜角度以及y轴的加速度,当然如果用到转向环还要读取z轴的角度。MPU-6050可读取3轴的角度以及3轴的加速度和芯片的温度。其中角度是直立环重要参数,角速度是速度环以及转向环的重要参数。获得6050的数据我们可以通过移植官方的DMP库,通过DMP,就可以使用InvenSense公司提供的运动处理资料库,非常方便地实现姿态解算,也可以获得原始数据后使用卡尔曼滤波解算姿态。
需要注意的是AD0接口如果接地,则6050的IIC地址为0x68,如果接vcc,则6050的IIC地址0x69参考资料 MPU-6050
PID
-
PID算法
上边运动分析中已经提及小车的运动需要直立环速度环以及转向环。PID的学习包括理论知识以及调参的经验等。
参考资料 PID算法原理、调试经验和代码z2r8
模块选型
主控模块
主控模块我们使用STM32f103c8t6 ,这款模块有3个通用定时器,一个高级定时器,3路串口,2路IIC,72Mhz,对于我们的需求是绰绰有余的。
编码器
编码器电机我们使用的是平衡之家的一款mini电机,由于打出的板子体型较小,所以没有选择体积大的电机。
额定电压7.4V,当然也可以工作在12V环境下,编码器供电5v。
价格差不了多少,还是体积大一点的搭建出来的小车看起来大气
电机线1/6接在电机模块的A/BOUT处,电机线2/5接5v以及接地,编码器AB相接在预留出来的定时器接口只有通道1/2支持
电机驱动模块
电机驱动模块选择的是TB6612模块,VM需要12v供电,SYBT需要5v供电否则电机不会转动, 同时输出两路PWM,同时控制两个电机。
这里其实一开始选择是功能更强大的A4950,但是因为摔了一下电机模块就直接罢工(太便宜),所以在购置硬件时千万不要图便宜,买好的不买便宜的
降压模块
降压模块选择一块带数显的,方便观察电池电量,不多叙述。
MPU-6050模块
不多赘述,同样6050也不要图便宜(这一块可能就是因为质量问题,导致程序无法进入6050的外部中断,最后没办法放入主程序运行)
蓝牙通讯模块
转向环则是实现小车转弯的一环。通常利用上位机与平衡小车的交互来来控制转向,这里使用的是蓝牙模块HC-06,HC-05/HC-06 /SPP-C这些通用的型号都可以用,蓝牙模块通过串口通信来传输数据。
硬件准备
IO口分配
定时器TIM4预留给OLED 每10ms刷新一次数据
定时器TIM3用于测距通过OLED展示距离
另外注意编码器只能用通道1/2
GPIO | 复用情况 | 连接外设 |
---|---|---|
PA2 | USART2_RX | 蓝牙 TX |
PA3 | USART2_TX | 蓝牙 RX |
PA0 | TIM2_CH1 | 电机A编码器A相 |
PA1 | TIM2_CH2 | 电机A编码器B相 |
PA6 | TIM3_CH1 | 电机B编码器B相 |
PA7 | TIM3_CH2 | 电机B编码器A相 |
PA8 | TIM1_CH1 | TB6612 PWMA |
PA11 | TIM1_CH4 | TB6612 PWMB |
PB1 | TIM3_CH3 | SR04 -> Trig |
PB0 | TIM3_CH4 | SR04 -> Echo |
PB5 | 无 | 6050外部中断 |
PB6 | IIC1 SCL | 6050 SCL |
PB7 | IIC1 SDA | 6050 SDA |
PB10 | IIC2 SCL | OLED SCL |
PB11 | IIC2 SDA | OLED SDA |
PB12 | 无 | BIN2 |
PB13 | 无 | BIN1 |
PB14 | 无 | AIN1 |
PB15 | 无 | AIN2 |
原理图绘制
原理图如图所示,供电接口采用的T型接口,电机等处加入100nf电容进行滤波。
PCB绘制
PCB未铺铜如图所示
铺铜以及添加泪滴效果
2D预览
实物图
硬件组装
3S电池搭建在最底层,中间放置降压模块,铜柱搭建最上方PCB,防止电机运动时的抖动影响系统的运行。
其实这里是有问题的,预留的OLED位置不够,导致只能放置在边上,PCB打孔处和降压模块冲突,整体排布也影响重心,需要调试机械中值。
实物图如下
硬件装配完成,下面我们开始软件编程
软件调试
程序是删减后的,并不完整 ,程序还是自己写学习才更有意义😀
配置编码器
#include "encoder.h"
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--PA0、PA1
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);//配置编码器模式 T1和T2的每个跳变沿均计数 TIM_ICPolarity_Rising:不反相。
/***********************
根据两个输入信号(TI1&TI2)的跳变顺序&