" 没想到一下鸽了几个月,遥想上一次更新还是在上海疫情在家期间?,忙碌了几个月生活终于回到了正轨,但愿以后不会那么忙碌了。好了我们言归正传,上一期我们学习了模板的建立,今天我们会利用STM32的定时器生成一组PWM驱动LED,并通过定时器中断改变其占空比,实现呼吸灯的效果。 "
??我们这次只使用一块STM32核心板并且基于上篇中实现的工程模板进行开发即可。开始之前,先介绍一下什么是PWM。
一、什么是PWM
??脉冲宽度调制(Pulse Width Modulation)是一种通过数字信号控制模拟器件的方式,它通过产生不同占空比(Duty Cycle)的矩形波来在输出端等效出不同电压值的控制效果,即对输出的脉冲宽度(占空比)进行调制,实现其对模拟器件的控制。下图展示了三种不同占空比PWM波的时间-幅度图: ??PWM的输出等效电压计算公式为:
U
o
=
V
C
C
?
t
T
=
V
C
C
?
D
u
t
y
(其中
t
为高电平时间,
T
为
P
W
M
周期)
U_o=VCC*\frac {t}{T}=VCC*Duty \\(其中t为高电平时间,T为PWM周期)
Uo?=VCC?Tt?=VCC?Duty(其中t为高电平时间,T为PWM周期) ??假设上述三种PWM波的VCC为5V,则上述三种等效输出电压分别为1.5V、2.5V、3.5V,即使用数字信号实现了模拟控制。
二、STM32定时器
??从官网上或使用CubeMX查询可知STM32F103RCT6属于大容量产品,共计有8个16位定时器,其中定时器1与定时器8为高级定时器;定时器2、3、4、5为通用定时器;定时器6、7为基本定时器。高级定时器与基本定时器都各有4个通道可以用来实现输入捕获或生成PWM等功能,同时高级定时器还具有输出互补PWM、刹车控制等功能。而基本定时器可以实现定时产生中断的基本功能。
??本次使用的LED连接在PA8上,查询F1的数据手册可知其可被复用为TIM1_CH1,即定时器1的输出通道1,可以使用该通道生成一个PWM。再使用定时器2生成一个周期性中断,改变PWM的占空比,生成一个占空比变化的PWM波,同时学习下定时器的使用方法。
??在HAL库中基本每个.C文件都对应了一个外设,里面封装了对该外设的各种操作函数,在文件中还有详细的注释描述其使用方法.例如定时器操作的源码就在stm32f1xx_hal_tim.c文件中,按照文件开始的注释中的说明,定时器或PWM生成的基本步骤为:
- 根据用途调用HAL_TIM_Base_MspInit()或HAL_TIM_PWM_MspInit()等函数进行初始化
- 使用__HAL_RCC_TIMx_CLK_ENABLE()使能定时器对应的时钟,设置使用的IO
- 根据用途调用HAL_TIM_PWM_ConfigChannel()等进行进一步设置
- 调用HAL_TIM_Base_Start()或HAL_TIM_PWM_Start()启动定时器
三、代码编写
为了方便后续管理,我将GPIO的部分与定时器的部分分开,并且开启定时器2的中断功能。
- GPIO部分负责初始化PA8端口,并设置为功能复用。gpio.c与gpio.h内容如下:
#include "gpio.h"
int GPIOInit(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
return 0;
}
#ifndef __GPIO__H__
#define __GPIO__H__
#ifdef __cplusplus
extern "C" {
#endif
#include "stm32f1xx_hal.h"
int GPIOInit(void);
#ifdef __cplusplus
}
#endif
#endif
- 定时器部分负责初始化定时器1与定时器2,设定的频率分别为1kHz与100Hz,并且设置定时器1输出PWM的占空比。timer.c与timer.h内容如下:
#include "timer.h"
TimerDef timer1, timer2;
int TimerInit(void)
{
TimerDef *tmpTim = &timer1;
tmpTim->TIM_Handle.Instance = TIM1;
tmpTim->TIM_Handle.Init.Prescaler = 72 - 1;
tmpTim->TIM_Handle.Init.Period = 1000;
__HAL_RCC_TIM1_CLK_ENABLE();
HAL_TIM_PWM_Init(&tmpTim->TIM_Handle);
tmpTim = &timer2;
tmpTim->TIM_Handle.Instance = TIM2;
tmpTim->TIM_Handle.Init.Prescaler = 72 - 1;
tmpTim->TIM_Handle.Init.Period = 10000;
__HAL_RCC_TIM2_CLK_ENABLE();
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
HAL_TIM_Base_Init(&tmpTim->TIM_Handle);
HAL_TIM_Base_Start_IT(&tmpTim->TIM_Handle);
return 0;
}
int TimerSetPulse(TimerDef *handle, uint32_t channel, float duty)
{
int t = handle->TIM_Handle.Init.Period * duty;
handle->TIM_OC_Handle.Pulse = t;
handle->TIM_OC_Handle.OCMode = TIM_OCMODE_PWM1;
HAL_TIM_PWM_ConfigChannel(&handle->TIM_Handle, &handle->TIM_OC_Handle, channel);
HAL_TIM_PWM_Start(&handle->TIM_Handle, channel);
return 0;
}
#ifndef __TIMER__H__
#define __TIMER__H__
#ifdef __cplusplus
extern "C" {
#endif
#include "stm32f1xx_hal.h"
typedef struct TimerDef
{
TIM_HandleTypeDef TIM_Handle;
TIM_OC_InitTypeDef TIM_OC_Handle;
}TimerDef;
int TimerInit(void);
int TimerSetPulse(TimerDef *handle, uint32_t channel, float duty);
extern TimerDef timer1, timer2;
#ifdef __cplusplus
}
#endif
#endif
- 定时器2中断函数:因为上面开启了定时器2中断,所以需要自行实现中断函数。这里在中断函数里每次增加PWM的1%占空比,增加到100%后变为每次减小1%,减小到0后再增加,如此往复。
void TIM2_IRQHandler(void)
{
static int duty = 0;
static int inc = 1;
HAL_TIM_IRQHandler(&timer2.TIM_Handle);
duty += inc;
if((duty >= 100) || (duty <= 0))
{
inc = -inc;
}
TimerSetPulse(&timer1, TIM_CHANNEL_1, duty / 100.0);
}
四、总结 ??至此我们就实现了呼吸灯代码的编写,我们使用了TIM1产生PWM驱动LED,使用TIM2产生中断修改PWM的占空比,让LED的亮度发生变化。
??通过示波器可以直观的看到该PWM的占空比是时刻在变化的,本期代码可以在文后网址中下载。下期我们来聊聊串口的使用,扫一扫下方二维码关注我,我们下期再见。
STM32实战(二):用定时器实现呼吸灯
END
|