前言:
本文是基于B站草履虫编写的平衡车相关内容,包括模块和基础知识,结合代码进行讲解,将知识进行汇总
(由于本篇内容较长,请结合目录使用)
注:基于开源精神,本文仅供学习参考
目录
本文是基于B站草履虫编写的平衡车相关内容,包括模块和基础知识,结合代码进行讲解,将知识进行汇总
[1.2.1 上拉电阻](#1.2.1 上拉电阻)
[1.3.2 IIC通信规则](#1.3.2 IIC通信规则)
1.基础知识
1.1输出模式
1.1.1推挽输出
如图推挽输出内部结构,PMOS管和NMOS管同一时间只有一个管子会导通
当PMOS管导通,此时输出一个高电平(VCC),称之为推
同理,当NMOS管导通,此时输出一个低电平(接地),称之为挽
1.1.2开漏输出
如图为开漏输出,与推挽输出相比,开漏少了一个PMOS管
当NMOS导通时,此时输出一个低电平(接地)
当NMOS断开时,此时电路为高阻态的状态(此时两个MOS管都为截断状态,可被视为电阻无穷大)
开漏输出不具有高电平的输出能力,所以我们外接一个上拉电阻(下面会讲)
使电路具备输出高电平的能力
1.1.3复用推挽和开漏输出
当我们使用高频芯片内部外设时(每秒钟电平变化上万次)
像 PWM、I2C、USART 等
使用复用功能会极大方便我们的编程
1.2电阻
1.2.1 上拉电阻
作用:将电路电平提升至一个高电平的状态(可将不确定电平拉为高电平)
1.2.2下拉电阻
作用:默认状态让电路为低电平,输入高电平时也可以让电路呈高电平状态
1.3IIC通信协议(I2C)
1.3.1总线的概念
非总线通信一般有:串口通信等
总线通信一般有:I2C通信等
总线优点:可以很轻松的增加与芯片通信的通信模块数量
1.3.2 IIC通信规则
这里我们着重介绍I2C (IIC - Inter - Integrated Circuit 内部集成电路)
SCL:传输时钟信号
SDA:传输数据信号
通信规则:
1.所有设备SCL/SDA引脚必须连接到主线SCL/SDA线上
2.同一时间只允许一个设备向外发送数据
3.仅主机主动产生SCL信号控制波特率(通信速度)
4.仅主机主动发起数据传输,从机无控制权(主机主动发送数据或接收数据)
5.传输方向:主机->从机,从机->主机,不存在从机->从机
1.3.3IIC通信数据的发送和接收
从机地址最后一个比特位(x)表示数据通信方向:
x=0:主机发数据给从机
x=1:主机从 从机读取数据
1.4欧拉角
欧拉角:以运动物体建立坐标系,通过绕轴旋转来表示运动物体当前的姿态
Yaw -偏航角:表示飞机绕Z轴旋转的角度,决定了飞机在水平方向上的朝向(决定飞机航行向)
Roll-翻滚角:表示飞机绕X轴旋转的角度
Pitch -俯仰角:表示飞机绕Y轴旋转的角度
一般我们可以通过加速度计和陀螺仪测量欧拉角
1.5H桥驱动电路
1.5.1基本电路图
Q1和Q3称为上桥
Q2和Q4称为下桥
单片机的4个IO口分别控制H桥的四个MOS(Qx)管,就可以实现电机的正反转、调速、刹车
(M为电机)
1.5.2正反转
打开Q1和Q4(假设此情况为正转)
电流方向Q1->M->Q4(如图所示)
打开Q3和Q2(那么此情况为反转)
1.5.3调速
单片机的PWM的输入可以给到上桥也可以给到下桥
调节PWM的占空比,占空比越大,电机转速越大,占空比越小电机转速越小
占空比即PWM在一个周期中高电平的时间与单位周期的比值
1.5.4死区
这里不过多解释,本质就是,单边上下桥不能同时导通(例如Q1和Q2不能同时导通)
否则会烧坏模块(即电源和GND短接)
1.5.5刹车
刹车基本原理:电机正负极相接
在H桥中我们可以同时打开两个上桥或两个下桥,实现刹车
1.6USART串口通信
1.6.1基本原理
在单片机中我们常用Tx表示发送端,Rx表示接收端,USART串口通信将Tx和Rx用导线相接
1.6.2波形原理
发送和接收的每条数据都有一条方形波
一条数据包括一个起始位和一个停止位
起始位永远是低电平,停止位永远是高电平
高电平发送二进制1
低电平发送二进制0
1-8为位代表数据位,为二进制,按十进制数值来算,最大可发送数字128,这个数字对应ASCII表
并且是倒过来读数
此图数据为01000001,对应ASCII码值为'A'
注意两个设备实现USART串口通信必须波特率相同
2.点亮LED
2.1模块原理图
2.2工程
2.2.1配置外部时钟
使用外部时钟的原因:比内部时钟更精准,所以通常情况我们都会使用外部时钟
打开STM32CubeMX并配置外部晶振(Crystal/Ceramic Resonator)
并将HCLK设置成72MHz(最大)
2.2.2配置引脚
打开PC13引脚
接着配置PC13引脚的模式
GPIO Output level -> Low
GPIO Mode -> Push Pull
GPIO Pull-up/Pull-down -> No
Maximum output speed -> High
User Label ->LED
- 根据上面的原理图,PC13连接LED负极,也就是说PC13高电平LED会灭,低电平LED就会亮
2.输出模式默认是推挽输出,开漏输出用的不多,特殊情况再用
3.推挽输出不需要上下拉电阻,推挽相当于直接接负极或正极
4.点亮LED对速度(芯片功耗)无要求,特殊情况再讨论
5.给PC13重新起名字 LED
2.2.3下载程序
在这里我们选择Serial Wire 的下载方式
因为我们的芯片就是通过电脑连接芯片SWCLK和SWDIO引脚下载程序的
2.2.4创建工程
注意IDE这一栏选择MDK-ARM
接着生成代码
驱动已经用软件配置好了,下面写一下主函数
cppint main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); while (1) { HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET); //亮 HAL_Delay(500); //延时500毫秒 HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET); //灭 HAL_Delay(500); } }
接着编译,调试(烧录)就行了
3.OLED模块
3.1模块配置
本项目使用的OLED模块配置
屏幕大小:0.96寸
像素点:128*64
PIN:
GND:OLED电源接地
VCC:OLED电源正极(可接3.3V/5V)
SCL:通信,时钟信号线(I2C)
SDA:通信,数据信号线(I2C)
3.2模块原理图
注意:通信引脚通常需要后期配置,这里是通用的原理图
3.3工程
3.3.1配置通信引脚
根据原理图,OLED通信引脚SCL- PB8 SDA-PB9
在软件左边配置框里找到I2C1并进行配置
其它选项默认即可
3.3.2配置时钟
跟LED一样,时钟设置为72兆
3.3.3配置OLED驱动代码
将驱动代码(驱动代码在文章结尾)放在指定路径里
OLED\Core\Inc 放入 oled.h oledfont.h
OLED\Core\Src 放入 oled.c
3.3.4简单改写驱动配置
在include 沙盒里添加 olde.h
在工程里把**.c** 文件添加进去(前面文件操作时**.h** 文件已经自动添加了,.c文件需手动添加)
在oled.h检查引脚
SCK ->PB8
SDA ->PB9
跟在Cube里设置的一样,不用修改
3.3.5显示数字
OLED_ShowNum(0,0,123456,6,16);
OLED_ShowNum函数五个参数:
x坐标-1代表1个像素点
y坐标-1代表8个像素点
显示数字
数字长度
数字大小(16像素:8*16,12像素:6*12)
3.3.6显示字符串
OLED_ShowString(0,2,"hello world!",16);
OLED_ShowString函数四个参数:
x坐标-1代表1个像素点
y坐标-1代表16个像素点
显示字符串
字符串大小 (16像素:8*16,12像素:6*12)
3.3.7显示汉字
着重说一下输出汉字
一个汉字像素------16*16
OLED_ShowCHinese(0,4,0);
OLED_ShowCHinese函数三个参数:
x坐标
y坐标
Hzk库
Hzk:汉字库
Hzk汉字库是用软件生成的,生成软件(PCtoLCD2002)在文章结尾
想要输入什么汉字,就用软件生成数组,写到Hzk库里,用0-x(字符数组)数字表示
3.3.8使用PCtoLCD2002生成汉字编码
首先设置基本格式
复制生成的编码到Hzk库里
3.3.9测试代码和现象
首先在主函数里添加这两个函数
OLED_Init(); //初始化
OLED_Clear();//清屏然后添加输出函数,并配置函数的参数,下面是主函数
cppint main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); OLED_Init(); //初始化 OLED_Clear();//清屏 OLED_ShowNum(0,0,123456,6,16); OLED_ShowString(0,2,"hello world!",16); OLED_ShowCHinese(0,4,0 ); OLED_ShowCHinese(16,4,1); OLED_ShowCHinese(32,4,2); while (1) { } }
结果
4.MPU6050
4.1MPU6050工作原理
MPU6050 内部整合了 3 轴陀螺仪和 3 轴加速度传感器,并且含有一个第二 IIC 接口,可用于连接外部磁力传感器即AUX_CL 和 AUX_DA,并利用自带的数字运动处理器(DMP: DigitalMotion Processor)硬件加速引擎,通过主 IIC 接口,向应用端输出完整的 9 轴融合演算数据
在mpu6050中我们基于欧拉角()用陀螺仪传感器测角度,用加速度传感器测加速度
4.2陀螺仪传感器
在MPU6050中有三轴陀螺仪
陀螺仪工作原理:测量角速度进而求出欧拉角
g(x/y/z):绕x/y/z轴旋转的角速度
计算公式:
4.3加速度传感器
在MPU6050中有三轴加速度计
加速度计工作原理:分解g(重力加速度),得到X/Y/Z三轴上的加速度
a(x/y/z):飞机在x/y/z轴上的加速度
利用三角关系求出各个方向上的加速度
4.4精确欧拉角
这两个传感器各有优缺点
我们根据实际情况分配给他们权重,以此来精确欧拉角
计算公式
α一般取值:
Δt:读取传感器的时间间隔(例如200Hz)
t:加速度计对陀螺仪纠偏时间(一般取0.1)
4.5模块原理介绍
4.6模块原理图
4.7工程
4.7.1创建工程
复制OLED工程
接着创建工程
将MPU6050驱动函数放入工程文件夹里
4.7.2设置软件IIC通信
打开工程,设置软件IIC通信(用软件编程的方式模拟IIC,因为PB3/4无IIC硬件接口)(硬件软件IIC通信效果无差别)
4.7.3添加.c文件
4.7.4添加.h文件
4.7.5详解代码与现象
main.c
初始化
定义三个角度的参数
在主函数里调用这三个参数
定义一个用于缓存的变量
添加库**#include "stdio.h",用于sprintf**
(sprintf函数打印到字符串中(要注意字符串的长度要足够容纳打印的内容,否则会出现内存溢出),printf函数打印输出到屏幕上)
姿态在OLED中的显示
语句sprintf((char*)display_buf, "pich:%.2f",pitch);解读:
因为OLED打印字符串,将display_buf强制类型转换成char类型,并将pitch打印到display_buf中
测试结果(数字为动态变化)
完整代码(仅显示头文件和进程部分)
cpp#include "main.h" #include "i2c.h" #include "gpio.h" #include "oled.h" #include "IIC.h" #include "inv_mpu.h" #include "inv_mpu_dmp_motion_driver.h" #include "mpu6050.h" #include "stdio.h" float pitch,roll,yaw; //定义三个角度的参数 uint8_t display_buf[20]; //显示缓存 void SystemClock_Config(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); OLED_Init(); //初始化 OLED_Clear();//清除 MPU_Init(); //初始化MPU mpu_dmp_init(); OLED_ShowString(0,00,"Init succeed",16); while (1) { HAL_Delay(10); mpu_dmp_get_data(&pitch,&roll,&yaw); sprintf((char*)display_buf, "pich:%.2f ",pitch); OLED_ShowString(0,2,display_buf,16); sprintf((char*)display_buf, "pich:%.2f ",roll); OLED_ShowString(0,4,display_buf,16); sprintf((char*)display_buf, "pich:%.2f ",yaw); OLED_ShowString(0,6,display_buf,16); } }
5.超声波模块
5.1基本工作原理
本项目使用HC-SR04 超声波测距模块,可提供 2cm-400cm(实际上到200cm ) 的非接触式距离感测功能,测距精度可达高到的非接触式距离感测功能,测距精度可达高到 3mm
(1)采用 IO 口 TRIG 触发测距,给最少 10us 的高电平信呈。
(2)模块自动发送 8 个 40khz 的方波,自动检测是否有信号返回;
(3)有信号返回,通过 IO 口 ECHO 输出一个高电平,高电平持续的时间就是超声
波从发射到返回的时间。公式
测试距离=(高电平时间*声速(340M/S))/2;
超声波时序图
原理:提供一个 10uS 以上脉冲触发信号,该模块内部将发出 8 个 40kHz 周期电平并检测回波。一旦检测到有回波信号则输出回响信号 。回响信号的脉冲宽度与所测的距离成正比。由此通过发射信号到收到的回响信号时间间隔可以计算得到距离
建议测量周期为 60ms 以上,以防止发射信号对回响信号的影响
该内容摘自HC-SR04超声波测距模块的原理介绍与代码实现-CSDN博客,详细内容参考以上链接
5.2模块原理图
Vcc:+5V电源供电
Trig:输入触发信号(可以触发测距)
Echo:传出信号回响(可以传回时间差)
Gnd:接地注:
1 、此模块不宜带电连接,若要带电连接,则先让模块的 GND 端先连接,否则会影响模块的正常工作。
2 、测距时,被测物体的面积不少于 0.5 平方米且平面尽量要求平整,否则影响测量的结果5.3工程
5.3.1思路
1.触发信号只需要输出10ms的高电平,所以触发引脚我们直接设置为GPIO(利用GPIO输出15ms的高电平)
2.回响信号使用外部中断(回响引脚配置成外部中断)
3.在回响信号上升沿时产生中断,利用单片机内部定时器定时,下降沿时再产生一次中断,这时读取定时器的值
4.利用定时器里的值(产生高电平的时间)根据公式计算结果
测试距离=高电平时间*声速(340M/S))/2;
5.3.2创建工程
创建MPU6050文件夹副本并改名为HC-SR04
打开副本里的CubeMx
5.3.3配置工程
设置
PA2-GPIO_EXTI2
PA3-GPIO_Output
将PA3引脚速度设置为High
选择PA2中断模式(上升沿下降沿都中断)
打开EXTI Line(不打开中断无任何作用)
5.3.4使能内部时钟
使能内部时钟并设置预分频系数为71
分频系数计算公式
5.3.5生成代码
5.3.5创建模块文件
创建并添加模块文件
sr04.c、sr04.h
5.3.6代码
main.c
cpp#include "main.h" #include "i2c.h" #include "tim.h" #include "gpio.h" #include "oled.h" #include "IIC.h" #include "inv_mpu.h" #include "inv_mpu_dmp_motion_driver.h" #include "mpu6050.h" #include "stdio.h" #include "sr04.h" float pitch,roll,yaw; //定义三个角度的参数 uint8_t display_buf[20]; //显示缓存 extern float distance; void SystemClock_Config(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_TIM3_Init(); OLED_Init(); //初始化 OLED_Clear();//清除 MPU_Init(); mpu_dmp_init(); OLED_ShowString(0,00,"Init succeed",16); while (1) { HAL_Delay(100); mpu_dmp_get_data(&pitch,&roll,&yaw); sprintf((char*)display_buf, "pich:%.2f ",roll); OLED_ShowString(0,2,display_buf,16); GET_Distance(); sprintf((char*)display_buf, "distance:%.2f ",distance); OLED_ShowString(0,4,display_buf,12); } }
sr04.c
cpp#include "sr04.h" uint16_t count; //1count=1us float distance; //保存距离(cm) extern TIM_HandleTypeDef htim3; //声明外部定义变量 void RCCdelay_us(uint32_t udelay) { __IO uint32_t Delay = udelay * 72 / 8;//(SystemCoreClock / 8U / 1000000U) //见stm32f1xx_hal_rcc.c -- static void RCC_Delay(uint32_t mdelay) do { __NOP(); } while (Delay --); }//us级延时函数 void GET_Distance(void) //发送触发信号 { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3,GPIO_PIN_SET); //输出高电平 RCCdelay_us(12); //信号持续时间为10us,保险起见设置为12us HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3,GPIO_PIN_RESET); //输出低电平 } //中断函数 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { //判断上升沿还是下降沿 if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_2)==GPIO_PIN_SET) { __HAL_TIM_SetCounter(&htim3,0); HAL_TIM_Base_Start(&htim3); //打开计时器时钟让它开始计时 } else { HAL_TIM_Base_Stop(&htim3); //停止计时 count = __HAL_TIM_GetCounter(&htim3); distance = count*0.017;//count/1000000*340*100/2; } }
sr04.h
cpp#ifndef _SR04_H #define _SR04_H #include "stm32f1xx_hal.h" void GET_Distance(void); #endif
5.3.7现象
6.电机驱动
6.1TB6612驱动模块
本项目使用TB6612模块驱动电机
6.1.2TB6612的引脚说明
6.1.3驱动原理
1. AIN1和AIN2、BIN1和BIN2分别与AO1、AO2和BO1、BO2配合使用来控制电机的方向和速度
2.通过PWM信号控制PWMA和PWMB可以调节电机的转速
3.YSTB用于控制模块的工作状态
4.VM和VCC分别提供电机驱动和逻辑电路的电源
5.该驱动模块为高电平时有效,否则,直流电机处于停止状态
具体参考本篇第1.5节
(本篇参考stm32学习探究:利用TB6612驱动直流电机-CSDN博客)
6.2工程
6.2.1创建工程
创建HC-SR04文件夹副本并改名为MOTOR
6.2.2打开定时器TIM
我们使用定时器TIM输出PWM波
打开PA11和PA8的时钟(对应原理图)
6.2.3配置预分频系数
配置预分频系数即配置PWM输出频率(常用10KHz)(两个TIM输出频率相同,占位比可不相同)
公式以及参数:
F:频率
HCLK:主时钟(72M)
PSC+1:预分频系数
ARR:用于设置定时器的计数周期
10^4:10KHz
7200:PSC=0,ARR=7199(数字可适当不同分配,71和99有利于调占空比)
6.2.4配置占空比
公式以及参数
1:期望的比值(全速)
Pulse:信号
6.2.5配置控制方向GPIO
AIN1:PB13
AIN2:PB12
BIN1:PB14
BIN2:PB15
6.2.6创建模块文件
motor.c和motor.h
6.2.7代码
main.c
cpp#include "main.h" #include "i2c.h" #include "tim.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "oled.h" #include "IIC.h" #include "inv_mpu.h" #include "inv_mpu_dmp_motion_driver.h" #include "mpu6050.h" #include "stdio.h" #include "sr04.h" #include "motor.h" float pitch,roll,yaw; //定义三个角度的参数 uint8_t display_buf[20]; //显示缓存 extern float distance; /* USER CODE END PV */ void SystemClock_Config(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_TIM3_Init(); MX_TIM1_Init(); /* USER CODE BEGIN 2 */ OLED_Init(); //初始化 OLED_Clear();//清除 MPU_Init(); mpu_dmp_init(); OLED_ShowString(0,00,"Init succeed",16); //使能PWM HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_4); Load(1000,1000); //转速 ,注意:1000可能数值有点小,如果出现轮子不动的情况,请改大数值 while (1) { HAL_Delay(100); mpu_dmp_get_data(&pitch,&roll,&yaw); sprintf((char*)display_buf, "pich:%.2f ",roll); OLED_ShowString(0,2,display_buf,16); GET_Distance(); sprintf((char*)display_buf, "distance:%.2f ",distance); OLED_ShowString(0,4,display_buf,12); } }
注意:1000可能数值有点小,如果出现轮子不动的情况,请改大数值
motor.c
cpp#include "motor.h" extern TIM_HandleTypeDef htim1; int abs(int p) { if(p>0) return p; else return -p; } //参数:左右电机转速 范围-7200~7200 void Load(int moto1,int moto2) { //两个 if(moto1<0) //左电机 { //AIN1和AIN0一个高电平一个低电平才能驱动电机,BIN同理 HAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET); } //配置占空比 __HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_4,abs(moto1)); if(moto2<0)//右电机 { HAL_GPIO_WritePin(GPIOB,GPIO_PIN_14,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(GPIOB,GPIO_PIN_14,GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_SET); } __HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_1,abs(moto2)); }
motor.h
cpp#ifndef _MOTOR_H #define _MOTOR_H #include "stm32f1xx_hal.h" void Load(int moto1, int moto2); #endif
6.2.8现象
两个轮以一定速度转动
7.编码器测速
为了方便大家理解,本篇放入<调试篇>讲解,谢谢大家
请大家下载 6.Encoder代码以便蓝牙模块的测试
8.蓝牙模块
8.1JDY-31蓝牙模块
本项目使用JDY-31蓝牙模块
8.2模块原理图
8.3工程
8.3.1创建Uart文件
将Encoder的副本重命名为Uart
8.3.2配置USART
1.打开PB10、PB11引脚,并设置为USART
2.设置基本的串口通信模式Asynchronous(异步)
3.打开全局中断(设备需要接收信息,需要打开全局中断)
记录和确保两台设备波特率相同
(参考8.1的数据)
Tx:接收数据
Rx:发送数据
(具体可参考1.6USART串口通信)
8.3.3生成代码
8.3.4发送数据
两种发送数据方式
8.3.5缓存数据
在 stm32f1xx_it.c中定义缓存数据数组
8.3.6接收中断
打开接收中断
rx_buf:缓存数组
1:每接收到一个字节就进入一次中断
每次中断需要执行HAL_UART_Receive_IT
总结(必看):
本篇资源来自:
【草履虫都能学会的STM32平衡小车教程(基础篇)】https://www.bilibili.com/video/BV1Gc411v73h?p=8\&vd_source=066060e5c72e1d7ec627401517cd9584(源代码部分修改)
【[铁头山羊stm32入门教程] 课程介绍】https://www.bilibili.com/video/BV1dQ4y1J7pD?vd_source=066060e5c72e1d7ec627401517cd9584
本项目资料汇总:
链接:https://pan.baidu.com/s/1uZ7UJ_uKbrhnDnoxRR4gAw?pwd=Ucar
提取码:Ucar
作者留言:本人学生党,制作不易,字数共1w+,如有侵权,有错误或不恰当的地方,及时沟通
如有任何问题可在评论区提问,3小时内解答
2024.9.20-2024.9.26