STM32实战(二):用定时器实现呼吸灯
“ 没想到一下鸽了几个月,遥想上一次更新还是在上海疫情在家期间☻,忙碌了几个月生活终于回到了正轨,但愿以后不会那么忙碌了。好了我们言归正传,上一期我们学习了模板的建立,今天我们会利用STM32的定时器生成一组PWM驱动LED,并通过定时器中断改变其占空比,实现呼吸灯的效果。 ”
我们这次只使用一块STM32核心板并且基于上篇中实现的工程模板进行开发即可。开始之前,先介绍一下什么是PWM。
一、什么是PWM
脉冲宽度调制(Pulse Width Modulation)是一种通过数字信号控制模拟器件的方式,它通过产生不同占空比(Duty Cycle)的矩形波来在输出端等效出不同电压值的控制效果,即对输出的脉冲宽度(占空比)进行调制,实现其对模拟器件的控制。下图展示了三种不同占空比PWM波的时间-幅度图:
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内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #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; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #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内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #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; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #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后再增加,如此往复。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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的占空比是时刻在变化的,本期代码可以在文后网址中下载。下期我们来聊聊串口的使用,扫一扫下方二维码关注我,我们下期再见。
https://github.com/ss302810694/STM32F103RCT6-Example
END