赞
踩
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%。
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 所示。
本实验通过三色LED灯的显示效果来展示PWM输出,原理图如图 26.2.1 所示,结合《数据手册.pdf》的引脚描述章节,可知:LED红色所接的PB0为TIM3的通道3;LED绿色所接的PB1为TIM3的通道4;LED蓝色所接的PB5重映射后为TIM3的通道2。
此外,当引脚为低电平时LED灯亮,因此PWM的占空比越高,LED越暗,PWM的占空比越低,LED灯越亮。
实验目的:本实验通过使用定时器的PWM输出功能,实现三色LED灯的红、绿、蓝组合,显示任意色,让读者理解PWM输出的设置方法。
本实验配套代码位于“5_程序源码\18_定时器—PWM输出\”。
代码段 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) // 频率 200kHz #define TIM_PERIOD (2000-1) // 周期 2000/f_tim = 10ms #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) * 输入参数:无 * 输出参数:无 * 返回值:无 * 函数作用:初始化定时器,输出频率 1Hz,占空比 50%的 PWMN 波 */ void TimerPWMInit(void) { TIM_ClockConfigTypeDef sClockSourceConfig; TIM_OC_InitTypeDef sConfig; // 定时器基本功能配置 hpwm.Instance = TIMx; // 指定定时器 TIM3 hpwm.Init.Prescaler = TIM_PRESCALER; // 预分频系数 PSC=360-1 hpwm.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数 hpwm.Init.Period = TIM_PERIOD; // 自动装载器 ARR 的值,ARR=2000-1 // 72MHz 经过 360 分频后,定时器时钟为 200KHz,即计数器每间隔 5us 计数一次,从 0 计数到 ARR,经历 10ms hpwm.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 定时器时钟不从 HCLK 分频 //hpwm.Init.RepetitionCounter = 0; // 重复计数器值,仅存在于高级定时器 hpwm.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; // 不自动重新装载 // 将 TIM3 按 PWM 模式初始化 if (HAL_TIM_PWM_Init(&hpwm) != HAL_OK) { Error_Handler(); } // 定时器时钟源选择 sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; // 选用内部时钟作为定时器时钟源 HAL_TIM_ConfigClockSource(&hpwm, &sClockSourceConfig); // 配置 PWM 的输出通道参数 sConfig.OCMode = TIM_OCMODE_PWM1; // PWM 输出的两种模式:PWM1 当极性为低,CCR<CNT,输出低电平,反之高电平 sConfig.OCPolarity = TIM_OCPOLARITY_LOW; // 设置极性为低(硬件上低电平亮灯) sConfig.OCFastMode = TIM_OCFAST_DISABLE; // 输出比较快速使能禁止(仅在 PWM1 和 PWM2 可设置) sConfig.Pulse = RGB_RED; // 在 PWM1 模式下,通道 3(RLED)占空比 if (HAL_TIM_PWM_ConfigChannel(&hpwm, &sConfig, TIM_RLED_CHANNEL) != HAL_OK) { Error_Handler(); } sConfig.Pulse = RGB_GREEN; // 在 PWM1 模式下,通道 4(GLED)占空比 if (HAL_TIM_PWM_ConfigChannel(&hpwm, &sConfig, TIM_GLED_CHANNEL) != HAL_OK) { Error_Handler(); } sConfig.Pulse = RGB_BLUE; // 在 PWM1 模式下,通道 2(BLED)占空比 if (HAL_TIM_PWM_ConfigChannel(&hpwm, &sConfig, TIM_BLED_CHANNEL) != HAL_OK) { Error_Handler(); } }
然后覆写定时器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) * 输入参数:htim-TIM 句柄 * 输出参数:无 * 返回值:无 * 函数作用:HAL_TIM_PWM_Init 回调硬件初始化 */ void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim) { GPIO_InitTypeDef GPIO_InitStruct; TIM_PWM_CLK_EN(); // PWM 所涉及的 TIM3 时钟使能 TIM_PWM_GPIO_CLK_EN(); // PWM 所涉及的 GPIOB 时钟使能 __HAL_RCC_AFIO_CLK_ENABLE(); // 重映射涉及时钟使能 __HAL_AFIO_REMAP_TIM3_PARTIAL(); // 启用 TIM3 重映射 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); // 使能 TIM3 中断 }
/* * 函数名:void TIM3_IRQHandler(void) * 输入参数:无 * 输出参数:无 * 返回值:无 * 函数作用:TIM3 中断的中断处理函数 */ void TIM3_IRQHandler(void) { HAL_TIM_IRQHandler(&hpwm); } /* * 函数名:void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) * 输入参数:htim-TIM 句柄 * 输出参数:无 * 返回值:无 * 函数作用:TIM 中断回调周期更新函数 */ void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIMx) { // 根据 R G B 值,修改对应通道的比较寄存器 CCR 的值(占空比) __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色彩定义:
最后使用“__HAL_TIM_SET_COMPARE()”,根据颜色色阶,设置每个通道的比较寄存器值。
代码段 26.3.5 按键中断处理回调函数(driver_key.c)
/* * 函数名:void HAL_GPIO_EXTI_Callback(void) * 输入参数:无 * 输出参数:无 * 返回值:无 * 函数作用:外部中断处理函数的回调函数,用以处理不同引脚触发的中断服务最终函数 */ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == KEY_UP_GPIO_PIN) // 按键 Up(KEY1)切换模式 { step = !step; rgb.rgb_red = 0; // 模式切换后,先关闭所有灯 rgb.rgb_green = 0; rgb.rgb_blue = 0; } if(GPIO_Pin == KEY_LEFT_GPIO_PIN) // 按键 Left(KEY3)调整红色 R { if (rgb.rgb_red > 255) rgb.rgb_red = 0; rgb.rgb_red = rgb.rgb_red + 10; } if(GPIO_Pin == KEY_DOWN_GPIO_PIN) // 按键 Down(KEY2)调整绿色 G { if (rgb.rgb_green > 255) rgb.rgb_green = 0; rgb.rgb_green = rgb.rgb_green + 10; } if(GPIO_Pin == KEY_RIGHT_GPIO_PIN) // 按键 Right(KEY4)调整蓝色 B { if (rgb.rgb_blue > 255) rgb.rgb_blue = 0; rgb.rgb_blue = rgb.rgb_blue + 10; } }
代码段 26.3.6 主函数控制逻辑(main.c)
// 初始化定时器 2 输出 PWM TimerPWMInit(); if (HAL_TIM_PWM_Start_IT(&hpwm, TIM_RLED_CHANNEL) != HAL_OK) // 启动红色 LED 所在通道定时器 PWM { Error_Handler(); } if (HAL_TIM_PWM_Start_IT(&hpwm, TIM_GLED_CHANNEL) != HAL_OK) // 启动绿色 LED 所在通道定时器 PWM { Error_Handler(); } if (HAL_TIM_PWM_Start_IT(&hpwm, TIM_BLED_CHANNEL) != HAL_OK) // 启动蓝色 LED 所在通道定时器 PWM { 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 颜色 { // 在按键中断中修改 R G B 值 // 在 TIM 中断中修改占空比 rgb.rgb_red = rgb.rgb_red%256; rgb.rgb_green = rgb.rgb_green%256; rgb.rgb_blue = rgb.rgb_blue%256; } }
本实验对应配套资料的“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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。