2020-12-21-两轮平衡小车探索

发布时间 2023-09-17 19:22:38作者: Yecgaa1

layout: post
title: 两轮平衡小车探索
categories: 日志
tags: 
    - 开发 
    - 开发任务
BGImage: 'https://github.xutongxin.me/https://raw.githubusercontent.com/xutongxin1/PictureBed/master/img0/20201220234325.png'
jekyll-theme-WuK:
    musicid: '744590'

16周

12.20

收到任务的第0天

首先平衡小车的样子大概是这样的

image-20201221002640667

这个是独轮子的,我可以降低难度用双轮子的,这样上面的平衡轮(好像我以前叫飞轮,产生左右动量的)就可以省略

那么前后就靠水平传感器来判断(但是这个检测周期必须快)

那么目前的任务点比较模糊但是可以先收集资料

本项目目前认为有以下难点

  1. AD应用
  2. PID算法
  3. 电控的算法逻辑
  4. 避障功能的实现
  5. 遥控功能的实现

image-20201221003447021

最后就是这次需要好好用todo任务列表了

12.21

确定方向

采用无刷电机和tb6612驱动板

做两轮的平衡小车

今天看完了PID

PID

P:比例,在计算中是输出=Kp*误差值。Kp越大误差调整越快(因为调整的力度大了),但是只有P容易过度(刹车不了,超过标准)

D:微分,在计算中距离对时间微分得到速度,越接近微分速度越小

I :积分,在运动中对误差积分,并防止最后的误差无法被缩小(存在外界摩擦风力等情况下)

个人感觉这个教程不错:

https://b23.tv/MyCc8f

PID实例图

img

通过获取e(误差),用PID算法算出所需的运行速度V,并且运行一下继续重新获得e

但是会出你想要的速度不是实际的速度(因为阻力是不确定的,不是你给一个对应的PWM就可以到对应速度)

所以需要多级PID

img

嵌套入速度PID的PWM调控方案!

还有一重是电流PID(不过可以不需要,对精度要求不高时),防止对电机的瞬时扭矩提出过高要求

速度获取需要编码器(终于明白它在PID的重要性)

12.22

大概了解了下

独轮小车确实比两轮的的难做不少

同时还要考虑转弯的算法

计划这两天试试搞个原理图

12.23

最小系统板的原理图怎么搞?

FOC(Field-Oriented Control),直译是磁场定向控制,也被称作矢量控制(VC,Vector Control),是目前无刷直流电机(BLDC)和永磁同步电机(PMSM)高效控制的最优方法之一。FOC旨在通过精确地控制磁场大小与方向,使得电机的运动转矩平稳、噪声小、效率高,并且具有高速的动态响应。

简单来说就是,FOC是一种对无刷电机的驱动控制方法,它可以让我们对无刷电机进行“像素级”控制,实现很多传统电机控制方法(比如电调)所无法达到的效果~

FOC的优势:

  1. 低转速下控制
    由于控制原理的区别,无刷电调只能控制电机工作在高转速下,低速下无法控制;而FOC控制器则完全没有这个限制,不论在什么转速下都可以实现精确控制。
  2. 电机换向
    同上面的理由,由于电调无法反馈转子位置,因此很难实现电机正反转的换向(当然有感电调可以实现);而FOC驱动器的换向性能极其优秀,最高转速下正反转切换可以非常顺畅;此外FOC还可以以能量回收的形式进行刹车控制。
  3. 力矩控制
    普通电调都只能控制电机转速,而FOC可以进行电流(力矩)、速度、位置三个闭环控制。
  4. 噪音
    FOC驱动器的噪音会比电调小很多,原因是普通电调采用方波驱动,而FOC是正弦波。

电调的优势:

  1. 兼容性
    电调驱动不同的BLDC不需要进行参数整定,而FOC需要。
  2. 算法复杂度
    电调的算法实现更简单,运算量少,很适合需要提高带宽的超高转速电机。
  3. 成本
    电调的成本比FOC低很多。

综上大家应该可以看出来,FOC驱动器在控制性能上是要比电调强大得多的,其优异的性能和磁场定向控制的原理是密不可分的,下面就会详细介绍FOC控制的实现方法。

12.24

生日不是不干活的理由啦,不过今天确实不会那么忙

AD的自动编号

工具-> 标注->原理图标注

img

USB-C使用

USB-C有很多脚的版本,最多24脚(12*2),而且难以焊接。在平时开发中如果需要使用,可以直接选择现成引出引脚的PCB板

img

D+和D-是USB 2.0差分信号,详情可以看这个

https://zh.wikipedia.org/wiki/USB_Type-C

同时如果涉及到USB接口布线规则可以看这个

https://zhuanlan.zhihu.com/p/76836808

保险丝的使用

保险丝一般用于输出原件后(比如电池后面),但是如果电源模块过于廉价也没有用保险的必要。Stm32等信号端一般没有用保险丝的意义

led放置

为了放置流经LED二极管电流过大,如果是3.3v接500欧电阻,5v接1K电阻

(LED灯一般流经电流10mA,电压大于3v左右就会亮)

12.25

圣诞节哦

AD的改图进入了第二版,总结一下第一版出现的问题

image-20201225192927810

应该要这样标注管脚名称

而不是这样

image-20201225193117949

用什么管脚才标记什么,不用全都标

一般也不用连线,而是直接标记就好

17周

12.29

刚考完试继续看

AD改图仍在继续

选元件(Component)的地方可以改封装再拖出来

image-20201229211656621

也可以在选完之后的选项里改

(footprint)

image-20201229211750553

至于封装管理器,抱歉确实有些问题没弄好,先放着不用吧

船型开关比较好应对大电流(如电机)

而自锁开关一般用于单片机电源

stm32cubeMX名词

Asynchronous(异步通信)和Synchronous(同步通信)的区别

https://blog.csdn.net/spdian/article/details/71215467

如果仅使用上的区别,就同步通信需要同步时钟,但是通信速度加快

Clock Source里的Internal Clock(内部时钟) ,另外一个ETR2是外部触发输入 。

12.30

I2C

个人翻译就是一个内存互访协议

但是一般都是单方面发起访问

可以读也可以写

而且有稳定性,通用性等优势

DMA

允许不同速度的硬件装置来沟通,而不需要依赖于CPU的大量中断负载

与I2C不同,它可用于设备内的内存与缓存区之间

18周

1.6

新年来了,又过了,继续写吧

目前打算是用的小车底盘

虽然确实挺贵的,但是也是为了买教程减少开发弯路吧

小车底盘上自带了编码器,我的磁性编码器就可以暂时不用了,但是这两周有机会还是会试一下的

1.9

小车底盘到了,首先是编码器从霍尔传感类变成了光栅类,虽然精度大大降低了(原来是0.03度,现在是0.7度)但是仍然很高

编码器的输出也不再是pwm或者i2c连接了,变成了AB相输出

AB相交叉输出电信号,每一个信号就像一个台阶,只要统计每分钟台阶数目就可以测出速度了

统计台阶的话转化为程序逻辑就是统计电平上升沿数量,即每当电平上升沿就进入中断,中断中把变量+1,再来个tim定时器每秒定时输出变量数值除以光栅数量就是每秒转了几圈就可以算出速度了

然后很方便的,cubemx里可以

1.30

出乎意料的,今天在帮myy解决led不亮的问题时,看到的电路图是这样的

image-20210130113620501

然后就想着,把PC13变成接地就好了嘛->就改成input嘛->怎么不亮???

怎么改成output拉低就可以了

然后开始检查自己的知识盲区是否存在

果然

https://blog.csdn.net/qq_38410730/article/details/79858906

这个文章很详细的解释了所有io模式的原理

同时

http://murata.eetrend.com/article/2017-10/1000901.html

VCC,VDD,VEE,VSS的解释

所以input不能当地

而output的low就是等效地,然后电势差点亮了灯

2.1

日常debug

满心欢喜的拿着别人的库准备测试,啥代码检查错误不报,编译时搞了个

No rule to make target `***', needed by `****'.  Stop.

你以为是库爆炸了

实际上你需要

重载cmake即可

(此问题仅限你在clion开发stm32的hal库开发时,可能的解决方案)

(如果还是无法解决问题,可以看看https://blog.csdn.net/wangcg123/article/details/77412636)

第二个bug

[  7%] Building C object CMakeFiles/mpu6050Test.elf.dir/Core/Src/MPU6050/I2C.c.obj
[  7%] Building C object CMakeFiles/mpu6050Test.elf.dir/Core/Src/MPU6050/inv_mpu.c.obj
[ 10%] Building C object CMakeFiles/mpu6050Test.elf.dir/Core/Src/MPU6050/inv_mpu_dmp_motion_driver.c.obj
In file included from E:\GitProject\stm32\mpu6050Test\Drivers\STM32F1xx_HAL_Driver\Inc/stm32f1xx_hal_rcc.h:1144,
                 from E:\GitProject\stm32\mpu6050Test\Core\Inc/stm32f1xx_hal_conf.h:237,
                 from E:\GitProject\stm32\mpu6050Test\Drivers\STM32F1xx_HAL_Driver\Inc/stm32f1xx_hal.h:30,
                 from E:\GitProject\stm32\mpu6050Test\Drivers\CMSIS\Device\ST\STM32F1xx\Include/stm32f1xx.h:200,
                 from E:\GitProject\stm32\mpu6050Test\Drivers\STM32F1xx_HAL_Driver\Inc/stm32f1xx_hal_def.h:30,
                 from E:\GitProject\stm32\mpu6050Test\Core\Inc/MPU6050/I2C.h:1,
                 from E:\GitProject\stm32\mpu6050Test\Core\Src\MPU6050\I2C.c:1:
E:\GitProject\stm32\mpu6050Test\Drivers\STM32F1xx_HAL_Driver\Inc/stm32f1xx_hal_rcc_ex.h:1859:1: error: unknown type name 'HAL_StatusTypeDef'; did you mean 'FLASH_TypeDef'?
 HAL_StatusTypeDef HAL_RCCEx_PeriphCLKConfig(RCC_PeriphCLKInitTypeDef  *PeriphClkInit);
 ^~~~~~~~~~~~~~~~~
 FLASH_TypeDef

而且有很多同类错误,在外网摸索后发现了问题来源

#include <stm32f1xx_hal_def.h>
#include <stm32f1xx_hal_conf.h>
#include "stm32f1xx_hal.h"//然而我包含上面两个

换而言之,被重复引用了。按照论坛说法

不必包括特定的HAL部分(在我的情况下为#include“ stm32f7xx_hal_gpio.h”),而仅需包括完整的HAL标头(#include“ stm32f7xx_hal.h”)。

所以把

#include <stm32f1xx_hal_def.h>
#include <stm32f1xx_hal_conf.h>

删去即可

同时这个错误不止发生在有

#include "stm32f1xx_hal.h" 

的情况中

[  7%] Building C object CMakeFiles/mpu6050Test.elf.dir/Core/Src/MPU6050/I2C.c.obj
[  7%] Building C object CMakeFiles/mpu6050Test.elf.dir/Core/Src/MPU6050/inv_mpu.c.obj
[ 10%] Building C object CMakeFiles/mpu6050Test.elf.dir/Core/Src/MPU6050/inv_mpu_dmp_motion_driver.c.obj
In file included from E:\GitProject\stm32\mpu6050Test\Drivers\STM32F1xx_HAL_Driver\Inc/stm32f1xx_hal_rcc.h:1144,
                 from E:\GitProject\stm32\mpu6050Test\Core\Inc/stm32f1xx_hal_conf.h:237,
                 from E:\GitProject\stm32\mpu6050Test\Drivers\STM32F1xx_HAL_Driver\Inc/stm32f1xx_hal.h:30,
                 from E:\GitProject\stm32\mpu6050Test\Drivers\CMSIS\Device\ST\STM32F1xx\Include/stm32f1xx.h:200,
                 from E:\GitProject\stm32\mpu6050Test\Drivers\STM32F1xx_HAL_Driver\Inc/stm32f1xx_hal_def.h:30,
                 from E:\GitProject\stm32\mpu6050Test\Core\Inc/MPU6050/I2C.h:1,
                 from E:\GitProject\stm32\mpu6050Test\Core\Src\MPU6050\I2C.c:1:
E:\GitProject\stm32\mpu6050Test\Drivers\STM32F1xx_HAL_Driver\Inc/stm32f1xx_hal_rcc_ex.h:1859:1: error: unknown type name 'HAL_StatusTypeDef'; did you mean 'FLASH_TypeDef'?
 HAL_StatusTypeDef HAL_RCCEx_PeriphCLKConfig(RCC_PeriphCLKInitTypeDef  *PeriphClkInit);
 ^~~~~~~~~~~~~~~~~
 FLASH_TypeDef

而且有很多同类错误,在外网摸索后发现了问题来源

#include <stm32f1xx_hal_def.h>
#include <stm32f1xx_hal_conf.h>
#include "stm32f1xx_hal.h"//然而我包含上面两个

换而言之,被重复引用了。按照论坛说法

不必包括特定的HAL部分(在我的情况下为#include“ stm32f7xx_hal_gpio.h”),而仅需包括完整的HAL标头(#include“ stm32f7xx_hal.h”)。

所以把

#include <stm32f1xx_hal_def.h>
#include <stm32f1xx_hal_conf.h>

删去即可

同时这个错误不止发生在有

#include "stm32f1xx_hal.h" 

的情况中,还可能出现在

[  3%] Building C object CMakeFiles/mpu6050Test.elf.dir/Core/Src/MPU6050/mpu6050.c.obj
In file included from E:\GitProject\stm32\mpu6050Test\Core\Src\MPU6050\mpu6050.c:1:
E:\GitProject\stm32\mpu6050Test\Drivers\CMSIS\Include/core_armv8mbl.h:1252:39: error: unknown type name 'IRQn_Type'; did you mean 'IPSR_Type'?
 __STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn)
                                       ^~~~~~~~~
                                       IPSR_Type

这种错误的解决是删去

#include <core_armv8mbl.h>

即可

最后来解决个warning

[  3%] Building C object CMakeFiles/mpu6050Test.elf.dir/Core/Src/MPU6050/mpu6050.c.obj
In file included from E:\GitProject\stm32\mpu6050Test\Core\Src\MPU6050\mpu6050.c:3:
E:\GitProject\stm32\mpu6050Test\Core\Inc/MPU6050/I2C.h:16: warning: "UNUSED" redefined
 #define UNUSED(x) ((void)(x))

In file included from E:\GitProject\stm32\mpu6050Test\Drivers\STM32F1xx_HAL_Driver\Inc/stm32f1xx_hal_rcc.h:29,
                 from E:\GitProject\stm32\mpu6050Test\Core\Inc/stm32f1xx_hal_conf.h:237,
                 from E:\GitProject\stm32\mpu6050Test\Drivers\STM32F1xx_HAL_Driver\Inc/stm32f1xx_hal.h:30,
                 from E:\GitProject\stm32\mpu6050Test\Core\Inc/MPU6050/I2C.h:2,
                 from E:\GitProject\stm32\mpu6050Test\Core\Src\MPU6050\mpu6050.c:3:
E:\GitProject\stm32\mpu6050Test\Drivers\STM32F1xx_HAL_Driver\Inc/stm32f1xx_hal_def.h:68: note: this is the location of the previous definition
 #define UNUSED(X) (void)X      /* To avoid gcc/g++ warnings */

就是被重复定义了,删掉#define UNUSED(x) ((void)(x))即可

2.2

代码笔记

printf重定向

/* USER CODE BEGIN Includes */
#include <stdio.h>
extern UART_HandleTypeDef huart1;   //声明串口
//如果UART_HandleTypeDef报错,是不是还没有在CubeMX里开串口?
/* USER CODE END Includes */
/* USER CODE BEGIN 0 */
#ifdef __GNUC__

#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)

PUTCHAR_PROTOTYPE
{
    //注意下面第一个参数是&husart1,因为cubemx配置了串口1自动生成的
    HAL_UART_Transmit(&huart1 ,(uint8_t*)&ch, 1, HAL_MAX_DELAY);
    //HAL_UART_Transmit_DMA(&huart1 ,(uint8_t*)&ch, 1);//DMA版本,未测试
    return ch;
}
#endif
/* USER CODE END 0 */

复制用普通:

#ifdef __GNUC__

#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)

PUTCHAR_PROTOTYPE
{
    HAL_UART_Transmit(&huart1 ,(uint8_t*)&ch, 1, HAL_MAX_DELAY);
    return ch;
}
#endif

DMA(不能使用):

#ifdef __GNUC__

#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)

PUTCHAR_PROTOTYPE
{
   	HAL_UART_Transmit_DMA(&huart1 ,(uint8_t*)&ch, 1);
    return ch;
}
#endif

回车换行问题,顺带重新记一下串口发送

'\r’是回车,前者使光标到行首
'\n’是换行,后者使光标下移一格

Unix系统里,每行结尾只有“<换行>”,即“\n”;
Windows系统里面,每行结尾是“<回车><换行>”,即“\r\n”;
Mac系统里,每行结尾是“<回车>”,即“\r

From:https://blog.csdn.net/M_Pinery/article/details/86308004
char RxBuffer[100]="Hello\r\n";
HAL_UART_Transmit(&huart1, (uint8_t *)&RxBuffer, sizeof(RxBuffer),0xFFFF);

而且测试得出先\r还是先\n好像结果一样,但是必须两个都要有

2.8

这几天都在重新画板,等板子,然后发现还是错的

有关的经验已经整理成另外一篇了

继续来看看PWM和定时器的实质问题

https://blog.csdn.net/as480133937/article/details/99231677

若配置脉冲计数器TIMx_CNT为向上计数,而重载寄存器TIMx_ARR配置为N,即TIMx_CNT的当前计数值数值X在TIMxCLK时钟源的驱动下不断累加,当TIMx_CNT的数值X大于N时,会重置TIMx_CNT数值为0重新计数。
而在TIMxCNT计数的同时,TIMxCNT的计数值X会与比较寄存器TIMx_CCR预先存储了的数值A进行比较,当脉冲计数器TIMx_CNT的数值X小于比较寄存器TIMx_CCR的值A时,输出高电平(或低电平),相反地,当脉冲计数器的数值X大于或等于比较寄存器的值A时,输出低电平(或高电平)。
如此循环,得到的输出脉冲周期就为重载寄存器TIMx_ARR存储的数值(N+1)乘以触发脉冲的时钟周期,其脉冲宽度则为比较寄存器TIMx_CCR的值A乘以触发脉冲的时钟周期,即输出PWM的占空比为A/(N+1)。

img

  • 1.选择TIM3
  • 2.设置定时器时钟源为内部时钟源
  • 设置定时器CH1为PWM模式
  • 3.对应管脚自动设置为复用模式
  • 4.可自行选择是否开启定时器中断

Channel1~4 就是设置定时器通道的功能 (输入捕获、输出比较、PWM输出、单脉冲模式)

img

  • Mode 选择PWM模式1
  • Pulse(占空比值) 先给0
  • Fast Mode PWM脉冲快速模式 : 和我们配置无关,不使能
  • PWM 极性: 设置为低电平 PS: 由于LED是低电平点亮,所以我们把极性设置为low

img

在 Parameter Settings 页配置预分频系数为 71,计数周期(自动加载值)为 499,定时器溢出频率,即PWM的周期,就是 72MHz/(71+1)/(499+1) = 2kHz

**PWM频率:**

**Fpwm =Tclk / ((arr+1)\*(psc+1))(单位:Hz)**

- arr 是计数器值
- psc 是预分频值

**占空比:**

- **duty circle = TIM3->CCR1 / arr(单位:%)**
- TIM3->CCR1 用户设定值

比如 定时器频率**Tclk** = 72Mhz arr=499  psc=71   那么PWM频率就是720000/500/72= 2000Hz,即2KHz

arr=499,TIM3->CCR1=250   则pwm的占空比为50% 

**改CCR1可以修改占空比,修改arr可以修改频率

2.10

用Clion做stm32的hal开发CMakeLists.txt又丢失了?

先普及一个特性,这个CMakeLists.txt是由clion生成的,不是CubeMX生成的。这个文件是在该项目刚第一次新建生成出现的,而且应该是在clion和CubeMX同时打开,且出现以下界面。要重新在clion新建项目

image-20210210174256089

Q:我的CubeMX是需要管理员打开的,Clion权限不够

A:clion管理员运行

Q:新建项目后,CubeMX一闪而过没有打开怎么办

A:再点击“在STM32CubeMX中打开”

Q:我已经在CubeMX配置配好了,结果现在让我重建???

A:

  1. 备份已经配置好的ioc文件,修改文件名为准备新建项目的项目名,项目名不得包含 .(自己采坑)
  2. 新建项目,然后覆盖掉默认的ioc文件。如果已经打开了CubeMX,可以先关闭CubeMX,覆盖,再在Clion里打开
  3. 生成文件,”愉快“ 继续开发

Q:为啥按上面方法还是不生成CMakeLists.txt?

A:项目名不得包含.(自己采坑)(如boat1.0项目名不合法)

2.14

..\OBJ\Bliz.axf: error: L6050U: The code size of this image (40034 bytes) exceeds the maximum allowed for this version of the linker.

https://blog.csdn.net/u011471873/article/details/49836603

原来是keil没激活

https://zhuanlan.zhihu.com/p/133396231激活方案

PWM程序编写进入了新的阶段

		Balance_Pwm =balance_UP(pitch,Mechanical_angle,gyroy);   //===平衡环PID控制	
		Velocity_Pwm=velocity(Encoder_Left,Encoder_Right);       //===速度环PID控制	 
  	if(1==Flag_Left||1==Flag_Right)    
		Turn_Pwm =turn(Encoder_Left,Encoder_Right,gyroz);        //===转向环PID控制
		else Turn_Pwm=(-0.5)*gyroz;
		Moto1=Balance_Pwm-Velocity_Pwm-Turn_Pwm;                 //===计算左轮电机最终PWM
		Moto2=Balance_Pwm-Velocity_Pwm+Turn_Pwm;                 //===计算右轮电机最终PWM
	 	Xianfu_Pwm();  						//===PWM限幅
		Turn_Off(pitch,Voltage);			 //===检查角度以及电压是否正常
		Set_Pwm(Moto1,Moto2);                                    //===赋值给PWM寄存器  

也逐渐理解了为何需要分贝算平衡环和速度环

需要额外了解的是PWM限幅和检查角度以及电压是否正常(否则关电机)

还有需要学一下转向环是怎么写的,一直没思路

2.21

今天处理最后的控制部分

先记录一下USART的接收代码

/* USER CODE BEGIN PV */
#define RXBUFFERSIZE  256     //最大接收字节数
char RxBuffer[RXBUFFERSIZE];   //接收数据
uint8_t aRxBuffer;            //接收中断缓冲
uint8_t Uart1_Rx_Cnt = 0;        //接收缓冲计数
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
/* USER CODE END 2 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    /* Prevent unused argument(s) compilation warning */
    UNUSED(huart);
    /* NOTE: This function Should not be modified, when the callback is needed,
             the HAL_UART_TxCpltCallback could be implemented in the user file
     */

    if (Uart1_Rx_Cnt >= 255)  //溢出判断
    {
        Uart1_Rx_Cnt = 0;
        memset(RxBuffer, 0x00, sizeof(RxBuffer));
        HAL_UART_Transmit(&huart1, (uint8_t *) "数据溢出", 10, 0xFFFF);

    } else {
        RxBuffer[Uart1_Rx_Cnt++] = aRxBuffer;   //接收数据转存

        if (RxBuffer[Uart1_Rx_Cnt - 1] == '#') //判断结束位,数据长度-1获取数据结尾确保数据完整性
        {
            //HAL_UART_Transmit(&huart1, (uint8_t *) &RxBuffer, Uart1_Rx_Cnt, 0xFFFF); //将收到的信息发送出去
            //while (HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
            
            //你的逻辑
            
            Uart1_Rx_Cnt = 0;//清空计数
            memset(RxBuffer, 0x00, sizeof(RxBuffer)); //清空数组
        }
    }

    HAL_UART_Receive_IT(&huart1, (uint8_t *) &aRxBuffer, 1);   //再开启接收中断
}

发送任意长度的字符串也可以

HAL_UART_Transmit(&huart1, (uint8_t *) &What_You_Want_to_send, sizeof(What_You_Want_to_send), 0xFFFF); 

编译错误:

CMakeFiles/BalanceCar.elf.dir/Core/Src/usart.c.obj:(.bss.huart1+0x0): multiple definition of `huart1';CMakeFiles/BalanceCar.elf.dir/Core/Src/main.c.obj:(.bss.huart1+0x0): first defined here

意思是多次重复的变量声明,在这里是删除

/* USER CODE BEGIN PTD */
UART_HandleTypeDef huart1;
/* USER CODE END PTD */

3.3

在电池问题解决之前整理下思绪吧

(怕现在不整理待会就因为某些原因忘了)

PID算法中,累加误差并乘以积分函数就是积分

速度微分出加速度,或者用几次误差的差来代替

所以对于角度调控

pid.balance=pid.Angle_Kp*pid.Bias+pid.Angle_Kd*Gyro;

对于电机的速度调控

pid.voltage=pid.Speed_Kp*pid.Speed_err+index*pid.Speed_Ki*pid.integral+pid.Speed_Kd*(pid.Speed_err-pid.Speed_err_last);

index是死区规避控制

然后还有转向环没有理解透彻

最后把三个环相加得到电机控制参数

传感器数据获取频率要低于电机参数控制频率防止控制速度过快达不到要求

电机频率控制放在定时器中断中,蓝牙数据获取放在串口中断,虽然原本可以用DMA先传进内存然后在main的while里处理,不过思考到数据量不大,应该可以正常运行

(为啥应该?我的电池QAQ)~

3.4

顺手总结定时器

定时器周期

img

TCLK=72MHZ

如TOUT=(5000*7200)/72 us = 500ms

image-20210304124958031

在main.c主函数上方初始化使能定时器2

  /* USER CODE BEGIN 2 */
    /*使能定时器中断*/
    HAL_TIM_Base_Start_IT(&htim2);
  /* USER CODE END 2 */

在main.c主函数下方添加中断回调函数

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    static unsigned char ledState = 0;
    if (htim == (&htim2))
    {
        if (ledState == 0)
            HAL_GPIO_WritePin(GPIOE,GPIO_PIN_15,GPIO_PIN_RESET);
        else
            HAL_GPIO_WritePin(GPIOE,GPIO_PIN_15,GPIO_PIN_SET);
        ledState = !ledState;
    }
}

编码器

首先是分频系数Prescaler 直接给0 ,Counter Period给65535(0xffff)

image-20210304133728737

Encoder Mode 如果是TI1的话就是只计数上升沿的脉冲,如果是TI2 andTI2 就是上下沿都计,脉冲是前一个的两倍,AB相合计4倍

HAL_TIM_Encoder_Start(&htimx, TIM_CHANNEL_ALL);//开启encoder
/**
  * 函数功能: 系统滴答定时器中断回调函数
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明: 每发生一次滴答定时器中断进入该回调函数一次
  */
void HAL_SYSTICK_Callback(void)
{
  static uint32_t count=0;
  if(start_flag) // 等待脉冲输出后才开始计时
  {
    time_count++;         // 每1ms自动增一
    if(time_count==1000)  // 1s
    {
      /* Get the current direction */
      uwDirection = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htimx_Encoder);     
      CaptureNumber=__HAL_TIM_GET_COUNTER(&htimx_Encoder);
     
      printf("当前编码器定时器计数方向:%d\n",uwDirection);
      printf("输入捕获值:%d\n",CaptureNumber);
      printf("1s内编码器计数值:%d\n",CaptureNumber>=count?CaptureNumber-count:CaptureNumber+65535-count);
     // 4 : 使用定时器编码器接口捕获AB相的上升沿和下降沿,一个脉冲*4.
      // 11:编码器线数(转速一圈输出脉冲数)
      // 34:电机减数比,内部电机转动圈数与电机输出轴转动圈数比,即减速齿轮比
      printf("电机实际转动速度:%0.2f(圈/s)\n",(float)(CaptureNumber>=count?CaptureNumber-count:CaptureNumber+65535-count)/11/34/2);
      count=CaptureNumber;
      time_count=0;
    }
  }
}

总结章

本次开发收获还是颇丰的

本次程序开源库:https://github.com/Yecgaa1/BalanceCar

顺带产物:

https://github.com/Yecgaa1/STM32_MPU6050_HAL_DMP

https://github.com/Yecgaa1/STM32_mpu6050_HAL_DMP_Libarary

https://github.com/Yecgaa1/PID_Example

参考资料:

大鱼电子Bliz平衡车资料V2.0

平衡小车之家资料

野火stm32资料盘

C Primer Plus 第6版 中文版