笔记:四旋翼无人机从0到1的实现(十一)无人机MCU驱动→PWM

Author:家有仙妻谢掌柜
Date:2021/2/18

以此篇文章记录自己的成长历程!
题外话:
这个小四轴无人机是大学时期学习制作的,加上现在工作学习对嵌入式的理解更加深入,因此想要重新梳理一下小四轴,之后在此基础上实现大四轴的飞控设计,这些都将在工作之余完成!
文末附有代码,代码之前的分析才是重点,建议看完整篇文章,本文将会深入浅出的分析PWM如何实现对电压的控制。

本实验
MCU:STM32F373CCT6
IDE:KEIL MDK5
实验目的,通过本实验完成PWM配置,深入理解PWM生成和控制的核心,PWM服务的对象空心杯电机将会在下一篇文章详述,链接在这里,喜欢的小伙伴可以去看看
空心杯电机传送门
本实验需要用到的资料:STM32F373xx数据手册传送门

一、PWM简介

PWM(Pulse Width Modulation)中文翻译为脉冲宽度调制,也就是调整脉冲在一个周期里高电平所占的时间(称其为:调节脉冲的占空比)。
PWM可以用来做什么呢?我们可以通过调节脉冲的占空比,来控制电压的大小(比如:100%的占空比时电压为12V,通过调节占空比让电压变成8V,3V甚至是0V,于是就实现了对输出电压的控制)。
那么这里就有一个问题了,为什么调节了占空比之后,输出电压就变化了呢?要理解这个问题就要用到中学物理学习到的等效面积法来解释,上图解释:

例如在本图中在1ms的周期时间里,满占空比100%时输出电压为12V,那么50%的占空比时(也即在周期时间内高电平所占的时间为0.5ms)那么高电平在整个周期内的面积也只有1/2了,此时输出电压就是12V*1/2 = 6V,也就是说我们通过调节了占空比从而实现了对电压大小的调节。
我们实现了对电压大小的控制,这个技能可就太厉害了,这样我们能做的事情可就太多了,比如:我们可以控制LED灯将其做成呼吸灯的效果,也可以控制一个直流电机的转速,实现电机的调速等等。这可就太巧了朋友们,我们要做的小四轴无人机正是需要能够对直流电机的电压大小实现调节。

上面啰里吧嗦讲了一堆,下面开始解释无人机如何让MCU产生四路PWM去控制我们的空心杯电机转动,STM32F373CCT6产生一个PWM波的过程。
经过刚才的介绍就知道了,在无人机工程中,想要实现对电机的控制就必须用到PWM,PWM依赖于周期时间,那么周期时间就是由时钟产生的,于是我们的重点落到了对Timer的使用上。

二、PWM需要配置什么?

这里就先把需要设定的拉出来,然后对这些配置在Timer部分进行分析
1.定时器使能
2.计数方向(向上计数、向下计数、中心对齐模式计数)
3.预分频系数的设定
4.重装载寄存器值的设定
5.捕捉/比较寄存器值的设定
6.PWM波模式(模式一、模式二)的设定
7.PWM波有效电平的设定
8.PWM波输出使能

三、Timer

通过STM32F373XX数据手册,能够发现可供使用的Timer是比较多的,结合我们四轴无人机的需求,我们是需要四路PWM来驱动电机,也就是需要四个通道的PWM波,本实验使用了三个定时器来生成这四路PWM波分别是TIM2的CH1,TIM5的CH1,TIM12的CH1,TIM12的CH2,每个定时器可能在某些功能上有差别,但定时器大多数的功能都是一样的。
于是这里只需要介绍如何用Timer2配置PWM波。
这里选用的是通用定时器,定时器之间是相互独立的,互不分享资源,各个定时器之间可以同步运行!
书接上一个部分,PWM需要配置什么?
先看图,这里简单分析一下,PWM需要我们配置的参数
图中横坐标表示的是时间t,纵坐标表示的是计数器的值TIMx_CNT,那么这个波形其实就是随着时间t的增加,TIMx_CNT值的变化情况。

其中,
TIMx_ARR:重装载寄存器的值
TIMx_CCR1:捕获/比较寄存器的值
TIMx_CNT:计数器的值

在这个图里我们需要关心的有哪些呢?
STM32F373CCT6的主时钟是72MHz的,我们可以不分频直接拿72MHz来用,这个72MHz表示定时器中计数器记一次数所需要的时间为1/72MHz,那么我们计数1000次所需要的的时间就是(1/72MHz)*1000也就是周期时间,那么计数1000次之后,把这1000次看作一个整体因为是一个PWM波的周期嘛,那么这个PWM波形的频率就是72KHz,我们知道周期时间T=1/f,其中f为频率,也称为载波频率,我们可以计算出这个周期时间是T=1/72kHz = 0.0139ms,那如果我们将主时钟分频呢?我们四分频一下就可以得到72MHz/4=18MHz,那么计数1000次作为一个周期,T=1/18kHz = 0.0556ms,于是我们得到了载波频率18KHz。
那么,到这里我们可以知道要配置的有:
①使能定时器时钟;
用到定时器,肯定是要把定时器时钟打开,这样才有72MHz过来。
②预分频系数;
要不要分频,或者几分频就在这里设置。
③重装载值TIMx_ARR;
上面讲的1000就是重装载值,也就是我们设定的周期时间内计数器记多少次数。
④计数方式;
是边沿对齐模式(向上计数,向下计数),还是中心对齐模式(向上下计数)
⑤捕获/比较寄存器的值的设定TIMx_CCR1;
这个值固定,那么占空比就固定了,这个值变化,PWM控制输出电压就会变化,于是这个值对应
的就是遥控器油门摇杆处理后的值。当然了我们还要将其设置为输出比较模式。
⑥PWM波模式(模式一、模式二)的设定 以及 PWM波有效电平的设定;
PWM模式一:
当TIMx_CNT < TIMx_CCR1,且向上计数时,输出电平为高或者为低为有效电平
当TIMx_CNT > TIMx_CCR1,且向下计数时,输出电平为高或者为低为无效电平
PWM模式二:
当TIMx_CNT < TIMx_CCR1,且向上计数时,输出电平为高或者为低为无效电平
当TIMx_CNT > TIMx_CCR1,且向下计数时,输出电平为高或者为低为有效电平
⑦PWM波输出使能。
到这里已经解释了上面说的(二、PWM需要配置什么?)的问题了。

我们直接操作寄存器,根据数据手册我们要用到的寄存器有:
1.TIMx_CR1(DataSheetP363)
在这里插入图片描述
TIMx_CR1寄存器是一个16位的寄存器,这里只用了10位

1.定时器使能
要使用这个定时器,就要使能定时器,那么我们实际操作的就是寄存器:TIMx_CR1中的第0位CEN位。

上代码

//小四轴无人机设计四个空心杯电机,需要四路PWM,这里的4个引脚分别来自于3个Timer
#include "pwm.h"

/*******************************************************************************
 * fuction	pwm_gpio_init
 * brief	GPIO初始化配置
 * param	无
 * return	无
 *******************************************************************************/  
void pwm_gpio_init(void)
{
    
    
	/*结构体变量定义*/
	GPIO_InitTypeDef  GPIO_InitStructure;
	/*开启引脚时钟*/
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA|RCC_AHBPeriph_GPIOB, ENABLE);
	/*开启引脚复用功能PB14/PB15*/
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource14, GPIO_AF_9);
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_9);
	/*开启引脚复用功能PA8/PA15*/
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_2);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource15,GPIO_AF_1);
	/*引脚的配置PB14/PB15*/
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_14|GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
	GPIO_Init(GPIOB,&GPIO_InitStructure);	
	/*引脚的配置PA8/PA15*/
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_8|GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
}
/*******************************************************************************
 * fuction	time2_config      
 * brief	定时器2初始化配置
 * param	无
 * return	无
 *******************************************************************************/  
void time2_config(void)
{
    
    
	/*结构体变量定义*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	/*开启time2时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	/*time2时基单元的配置*/
	TIM_TimeBaseStructure.TIM_Prescaler 		= 3;
	TIM_TimeBaseStructure.TIM_CounterMode 		= TIM_CounterMode_Up;
	TIM_TimeBaseStructure.TIM_Period 			= 1000;
	TIM_TimeBaseStructure.TIM_ClockDivision 	= TIM_CKD_DIV1;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);
	/*CH1 pwm1 模式配置*/
	TIM_OCInitStructure.TIM_OCMode 				= TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState 		= TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse 				= 0;
	TIM_OCInitStructure.TIM_OCPolarity 			= TIM_OCPolarity_High;
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);
	TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
	TIM_ARRPreloadConfig(TIM2, ENABLE);
	TIM_Cmd(TIM2, ENABLE);
}
/*******************************************************************************
 * fuction	time5_config     
 * brief	定时器5初始化配置
 * param	无
 * return	无
 *******************************************************************************/
void time5_config(void)
{
    
    
	/*结构体变量定义*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	/*开启time2时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
	/*time2时基单元的配置*/
	TIM_TimeBaseStructure.TIM_Prescaler 		= 3;
	TIM_TimeBaseStructure.TIM_CounterMode 		= TIM_CounterMode_Up;
	TIM_TimeBaseStructure.TIM_Period 			= 1000;
	TIM_TimeBaseStructure.TIM_ClockDivision 	= TIM_CKD_DIV1;
	TIM_TimeBaseInit(TIM5,&TIM_TimeBaseStructure);
	/*CH1 pwm1 模式配置*/
	TIM_OCInitStructure.TIM_OCMode 				= TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState 		= TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse 				= 0;
	TIM_OCInitStructure.TIM_OCPolarity 			= TIM_OCPolarity_High;
	TIM_OC1Init(TIM5, &TIM_OCInitStructure);
	TIM_OC1PreloadConfig(TIM5, TIM_OCPreload_Enable);
	TIM_ARRPreloadConfig(TIM5, ENABLE);
	TIM_Cmd(TIM5, ENABLE);
}
/*******************************************************************************
 * fuction	time12_config     
 * brief	定时器12初始化配置
 * param	无
 * return	无
 *******************************************************************************/
void time12_config(void)
{
    
    
	/*结构体变量定义*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_OCInitTypeDef TIM_OCInitStructure;
	/*开启time2时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM12, ENABLE);
	/*time2时基单元的配置*/
	TIM_TimeBaseStructure.TIM_Prescaler 		= 3;
	TIM_TimeBaseStructure.TIM_CounterMode 		= TIM_CounterMode_Up;
	TIM_TimeBaseStructure.TIM_Period 			= 1000;
	TIM_TimeBaseStructure.TIM_ClockDivision 	= TIM_CKD_DIV1;
	TIM_TimeBaseInit(TIM12,&TIM_TimeBaseStructure);
	/*CH1 pwm1 模式配置*/
	TIM_OCInitStructure.TIM_OCMode 				= TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState 		= TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse 				= 0;
	TIM_OCInitStructure.TIM_OCPolarity 			= TIM_OCPolarity_High;
	TIM_OC1Init(TIM12, &TIM_OCInitStructure);
	TIM_OC1PreloadConfig(TIM12, TIM_OCPreload_Enable);
	TIM_OC2Init(TIM12, &TIM_OCInitStructure);
	TIM_OC2PreloadConfig(TIM12, TIM_OCPreload_Enable);
	TIM_ARRPreloadConfig(TIM12, ENABLE);
	TIM_Cmd(TIM12, ENABLE);
}
/*******************************************************************************
 * fuction	pwm_init    
 * brief	PWM初始化函数封装
 * param	无
 * return	无
 *******************************************************************************/
void pwm_init(void)
{
    
    
	 pwm_gpio_init();
	 time2_config();
	 time5_config();
	 time12_config();
}
#ifndef _PWM_H__
#define _PWM_H__

#include "board_define.h"

void pwm_init(void);

#endif

猜你喜欢

转载自blog.csdn.net/FutureStudio1994/article/details/113853254