26.1 关于 PWM
26.1.1 PWM 介绍
PWM(Pulse Width Modulation,脉冲宽度调制)。是一种利用微处理器的数字输出来对模拟电路进行控制的技术,广泛应用在测量、通信、功率控制等诸多领域。
举个最常见的例子,利用PWM控制显示屏亮度。屏幕背光可以看作是一个大灯,这个大灯只有亮、灭两种状态。如果把灯亮看作100%,灯灭看作0%,要实现50%的亮度,可以在某个单位时间里亮灯50%时间、灭灯50%时间,只要这个单位时间够小,由于人眼具有视觉暂留效应,就会从宏观的感觉整个灯是一直亮着,且亮度只要原来的一半。
PWM实质就是GPIO不断翻转输出高、低电平,这个效果可以写代码控制GPIO产生,但这样就会占用CPU,CPU就不方便做其它事情。此时可以利用定时器,设置好翻转时间,让其自动控制GPIO翻转,无需CPU再参与。
在一个周期内,高电平占整个信号周期的百分比,称之为占空比(Duty Cycle),如图 26.1.1 所示,占空比分别为30%、50%、70%。
26.1.2 STM32 的 PWM
PWM是定时器输出比较的典型应用。除STM32的基本定时器(TIM6、TIM7)外,其它定时器都支持PWM输出,每个通用定时器(TIM2、TIM3、TIM4、TIM5)可以同时产生4路PWM,每个高级定时器(TIM1、 TIM8)可以同时产生多达7路PWM。
以通用定时器为例,如上图 25.1.2 所示,每个定时器有四路输出通道:TIMx_CH1、TIMx_CH2、TIMx_CH2、TIMx_CH4,每个通道都对应一个捕获/比较寄存器:TIMx_CCR1\TIMx_CCR2、TIMx_CCR3、 TIMx_CCR4。将计数器CNT的值,与捕获/比较寄存器相比较,由比较结果决定输出电平高低,从而实现PWM输出。
举个例子,若当前设置计数器为向上计数,定时器重载值为TIMx_ARR,通道1的捕获/比较寄存器值为TIMx_CCR1。如图 26.1.2 所示,首先定时器从0开始计数,在0t1时间段,TIMx_CNT<TIMx_CCR1,输出低电平;在t1t2时间段,TIMx_CNT>TIMx_CCR1,输出高电平;t2时,TIMx_CNT=TIMx_ARR计数器溢出,重新从0开始,如此循环。
由此可以看出,TIMx_ARR决定PWM的周期,TIMx_CCR1决定PWM的占空比,此时占空比计算公式为:
每个定时器的输出比较模式共同8种(通过配置寄存器CCMRx的位OCxM [2:0]选择),其中有两种是最常用的PWM输出模式:PWM模式1和PWM模式2。两种模式区别在于,计数器CNT与TIMx_CCRx比较的结果,输出的电平不同,如表 26.1.1 所示。
26.2 硬件设计
本实验通过三色LED灯的显示效果来展示PWM输出,原理图如图 26.2.1 所示,结合《数据手册.pdf》的引脚描述章节,可知:LED红色所接的PB0为TIM3的通道3;LED绿色所接的PB1为TIM3的通道4;LED蓝色所接的PB5重映射后为TIM3的通道2。
此外,当引脚为低电平时LED灯亮,因此PWM的占空比越高,LED越暗,PWM的占空比越低,LED灯越亮。
26.3 软件设计
26.3.1 软件设计思路
实验目的:本实验通过使用定时器的PWM输出功能,实现三色LED灯的红、绿、蓝组合,显示任意色,让读者理解PWM输出的设置方法。
- 初始化定时器相关参数:配置时钟、工作方式、PWM模式等;
- 初始化定时器涉及的硬件相关参数:初始化涉及的时钟、引脚、中断;
- 在定时器中断函数里,修改占空比;
- 在按键中断函数里,切换LED模式和修改R G B值;
- 主函数编写控制逻辑:实现随机模式和用户模式。随机模式即间隔1s,随机生成R G B值显示;用户模式即用户按键修改R G B值,显示自己需要的颜色。
本实验配套代码位于“5_程序源码\18_定时器—PWM输出\”。
26.3.2 软件设计讲解
- GPIO选择与接口定义
宏定义涉及的定时器、周期、引脚等,如代码段 26.3.1 所示。
代码段 26.3.1 相关宏定义(driver_timer.h)
typedef struct
{
uint8_t rgb_red;
uint8_t rgb_green;
uint8_t rgb_blue;
}RGB;
#define TIMx TIM3
#define TIMx_IRQn TIM3_IRQn
#define TIMx_IRQHandler TIM3_IRQHandler
#define TIM_PRESCALER ((36*10)-1)
#define TIM_PERIOD (2000-1)
#define RGB_RED (0)
#define RGB_GREEN (0)
#define RGB_BLUE (0)
#define TIM_RLED_PIN GPIO_PIN_0
#define TIM_RLED_PORT GPIOB
#define TIM_RLED_CHANNEL TIM_CHANNEL_3
#define TIM_GLED_PIN GPIO_PIN_1
#define TIM_GLED_PORT GPIOB
#define TIM_GLED_CHANNEL TIM_CHANNEL_4
#define TIM_BLED_PIN GPIO_PIN_5
#define TIM_BLED_PORT GPIOB
#define TIM_BLED_CHANNEL TIM_CHANNEL_2
#define TIM_PWM_CLK_EN() __HAL_RCC_TIM3_CLK_ENABLE()
#define TIM_PWM_GPIO_CLK_EN() __HAL_RCC_GPIOB_CLK_ENABLE()
首先初始化定时器PWM相关参数,如代码段 26.3.2 所示。 代码段 26.3.2 定时器 PWM 初始化(driver_timer.c)
void TimerPWMInit(void) {
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_OC_InitTypeDef sConfig;
hpwm.Instance = TIMx;
hpwm.Init.Prescaler = TIM_PRESCALER;
hpwm.Init.CounterMode = TIM_COUNTERMODE_UP;
hpwm.Init.Period = TIM_PERIOD;
hpwm.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
hpwm.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_PWM_Init(&hpwm) != HAL_OK)
{
Error_Handler(); }
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&hpwm, &sClockSourceConfig);
sConfig.OCMode = TIM_OCMODE_PWM1;
sConfig.OCPolarity = TIM_OCPOLARITY_LOW;
sConfig.OCFastMode = TIM_OCFAST_DISABLE;
sConfig.Pulse = RGB_RED;
if (HAL_TIM_PWM_ConfigChannel(&hpwm, &sConfig, TIM_RLED_CHANNEL) != HAL_OK)
{
Error_Handler(); }
sConfig.Pulse = RGB_GREEN;
if (HAL_TIM_PWM_ConfigChannel(&hpwm, &sConfig, TIM_GLED_CHANNEL) != HAL_OK)
{
Error_Handler(); }
sConfig.Pulse = RGB_BLUE;
if (HAL_TIM_PWM_ConfigChannel(&hpwm, &sConfig, TIM_BLED_CHANNEL) != HAL_OK)
{
Error_Handler(); } }
- 14行:选择配置哪一个定时器;
- 15行:设置定时器时钟预分频系数PSC,这里设置为360-1,则72MHz经过360分频后,定时器时钟为200KHz,
即定时器计数1次的时间为5us; - 16行:设置定时器计数方式,这里为向上计数:
- 17行:设置自动装载器ARR的值,这里设置为2000-1,则计数器从0开始计数到2000,周期为10ms;
- 20行:设置时钟分频,用于计数器工作时滤除高频干扰,本实验不涉及,任意即可;
- 21行:设置重复计数器值,仅存在于高级定时器,这里使用的TIM2为通用定时器,不涉及;
- 22行:设置是否定时器自动重新装载,本实验不需要自动装载;
- 25~28行:将定时器按PWM功能初始化,同时该函数会调用“HAL_TIM_PWM_MspInit()”进行硬件相关初始化;
- 31~32行:设置内部时钟作为定时器时钟源;
- 35~36行:设置PWM的模式和极性。这里设置PWM1模式,极性为低,与LED灯低电平亮灯对应;此时,在周期和ARR确定的情况下,计数器CNT从0到CCR,输出低电平,LED灯亮,计数器CNT从CCR到ARR,输出高电平,LED灯灭。即,CCR值越小,占空比越大,灯越暗,CCR值越大,占空比越小,灯越亮,ARR值与亮度成正比;
- 37行:输出比较快速模式,可减少输出延时,可以不使用禁止;
- 38~54行:分别设置三色灯所对应的三个通道的占空比和前面的输出通道参数;当前占空比为0,后面代码再修改占空比值;
然后覆写定时器PWM硬件相关初始化,如代码段 26.3.3 所示。因为蓝色LED灯PB5,需要重映射才能是TIM3_CH2功能,这里需要使能重映射相关的AFIO(Alternate function I/O)时钟,然后使用HAL函数“__HAL_AFIO_REMAP_TIM3_PARTIAL()”启动TIM3重映射,同时将PB5设置为复用功能,此时PB5才能作为TIM3_CH2功能。
代码段 26.3.3 定时器 PWM 硬件相关初始化(driver_timer.c)
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_PWM_CLK_EN();
TIM_PWM_GPIO_CLK_EN();
__HAL_RCC_AFIO_CLK_ENABLE();
__HAL_AFIO_REMAP_TIM3_PARTIAL();
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Pin = TIM_RLED_PIN;
HAL_GPIO_Init(TIM_RLED_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = TIM_GLED_PIN;
HAL_GPIO_Init(TIM_GLED_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = TIM_BLED_PIN;
HAL_GPIO_Init(TIM_BLED_PORT, &GPIO_InitStruct);
HAL_NVIC_SetPriority(TIMx_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIMx_IRQn);
}
- TIM中断处理函数
当定时器TIM3计数溢出时,会进入中断处理“TIM3_IRQHandler()”,在中断函数里,根据RGB值,设置比较寄存器CCR的值,也就实现了修改占空比,如代码段 26.3.4 所示。 代码段 26.3.4 TIM3 中断处理函数(driver_timer.c)
void TIM3_IRQHandler(void) {
HAL_TIM_IRQHandler(&hpwm); }
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIMx) {
__HAL_TIM_SET_COMPARE(&hpwm, TIM_RLED_CHANNEL, rgb.rgb_red *2000/255);
__HAL_TIM_SET_COMPARE(&hpwm, TIM_GLED_CHANNEL, rgb.rgb_green*2000/255);
__HAL_TIM_SET_COMPARE(&hpwm, TIM_BLED_CHANNEL, rgb.rgb_blue *2000/255); }
}
这里简单补充下一些色彩理论知识,常见的色彩定义标准有四种: ①HSB:基于人眼视觉的颜色模式; ②RGB:基于光色的颜色模式,是加色模式,两者组合变亮; ③CMYK:基于印刷颜料的颜色模式,是减色模式,两者组合变暗; ④Lab:基于人对颜色的感觉,与设备无关,色域宽阔; LED灯属于发光色,RGB三个颜色通道的变化,以及它们相互之间的叠加来得到各式各样的颜色的,几乎包括了人类视力所能感知的所有颜色,RGB色彩定义:
- R(Red):0~255阶,一共256阶色;
- G(Green):0~255阶,一共256阶色;
- B(Blue):0~255阶,一共256阶色;
因此,这里把每个颜色的LED灯最暗用0表示,LED灯最亮用255表示,而整个计数周期为2000,因此传入的比较寄存器值为:
最后使用“__HAL_TIM_SET_COMPARE()”,根据颜色色阶,设置每个通道的比较寄存器值。
- 按键功能
根据需求,这里使用按键进行模式切换和RGB颜色调整。初始化按键后,在按键中断处理函数中添加每个按键的功能,如代码段 26.3.5 所示
代码段 26.3.5 按键中断处理回调函数(driver_key.c)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == KEY_UP_GPIO_PIN)
{
step = !step;
rgb.rgb_red = 0;
rgb.rgb_green = 0;
rgb.rgb_blue = 0; }
if(GPIO_Pin == KEY_LEFT_GPIO_PIN)
if (rgb.rgb_red > 255)
rgb.rgb_red = 0;
rgb.rgb_red = rgb.rgb_red + 10; }
if(GPIO_Pin == KEY_DOWN_GPIO_PIN)
if (rgb.rgb_green > 255)
rgb.rgb_green = 0;
rgb.rgb_green = rgb.rgb_green + 10; }
if(GPIO_Pin == KEY_RIGHT_GPIO_PIN)
if (rgb.rgb_blue > 255)
rgb.rgb_blue = 0;
rgb.rgb_blue = rgb.rgb_blue + 10; } }
- 10~17行:当按键KEY1_UP按下后,修改模式标志位setp,同时清除RGB的初始值;
- 19~24行:当按键KEY3_LEFT按下后,增加红色LED灯亮度(占空比);
- 26~31行:当按键KEY2_DOWN按下后,增加绿色LED灯亮度(占空比);
- 19~24行:当按键KEY3_RIGH按下后,增加蓝色LED灯亮度(占空比);
- 主函数控制逻辑
在主函数里依次初始化时钟、串口、按键,定时器PWM等,便可以启动PWM输出了,如代码段 26.3.6所示。
代码段 26.3.6 主函数控制逻辑(main.c)
TimerPWMInit();
if (HAL_TIM_PWM_Start_IT(&hpwm, TIM_RLED_CHANNEL) != HAL_OK)
{
Error_Handler(); }
if (HAL_TIM_PWM_Start_IT(&hpwm, TIM_GLED_CHANNEL) != HAL_OK)
{
Error_Handler(); }
if (HAL_TIM_PWM_Start_IT(&hpwm, TIM_BLED_CHANNEL) != HAL_OK)
{
Error_Handler(); }
while(1) {
if(step==0)
rgb.rgb_red = rand()%256;
rgb.rgb_green = rand()%256;
rgb.rgb_blue = rand()%256;
HAL_Delay(1000); }
else
{
rgb.rgb_red = rgb.rgb_red%256;
rgb.rgb_green = rgb.rgb_green%256;
rgb.rgb_blue = rgb.rgb_blue%256; } }
- 2行:初始化定时器PWM;
- 4~7行:使用带中断的方式启动定时器PWM,传入红色LED所在通道;
- 8~11行:使用带中断的方式启动定时器PWM,传入绿色LED所在通道;
- 12~15行:使用带中断的方式启动定时器PWM,传入蓝色LED所在通道;
- 17行~34行:主循环
- 19-25行:如果KEY1_UP按下,则中断会修改step为1,进入该判断中;使用C库的“rand()”函数产生一个随机数,然后使用“%”取余,得到一个1~255之间的随机数,该随机数作为颜色色阶,定时器PWM中断产生后,便以此值作为占空比,实现LED亮度的修改;
- 26~33行:用户模式下,无需任何操作,在按键中断中修改RGB值,在TIM中断中修改占空比,实现用户分别控制RGB亮度;
26.4 实验效果
本实验对应配套资料的“5_程序源码\18_定时器—PWM输出\”。打开工程后,编译,下载,串口提示如图 26.4.1 所示。
此时可以看到三色LED灯显示随机颜色(注意不要长时间直视三色灯,可使用薄纸巾盖住观察),按下KEY1_UP键后,三色LED不再随机变化,分别按下KEY3_LEFT、KEY2_DOWN、KEY4_RIGH可分别控制红色、绿色、蓝色的亮度。
百问网技术论坛: http://bbs.100ask.net/
百问网嵌入式视频官网: https://www.100ask.net/index
百问网开发板: 淘宝:https://100ask.taobao.com/ 天猫:https://weidongshan.tmall.com/
技术交流群2(鸿蒙开发/Linux/嵌入式/驱动/资料下载) QQ群:752871361
单片机-嵌入式Linux交流群: QQ群:536785813
韦东山嵌入式培训班交流群③ QQ群:717041375
|