一.PWM简介
PWM是 Pulse Width Modulation 的缩写,中文意思就是脉冲宽度调 制,简称脉宽调制。 PWM是一种对模拟信号电平进行数字编码 的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个 具体模拟信号的电平进行编码。PWM 信号仍然是数字的,因为在给定的 任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压 或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去 的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被 断开的时候。只要带宽足够,任何模拟值都可以使用 PWM 进行编码。
PWM生成方法
计算法
根据拟合波形的频率、幅值和半周期脉冲数,准确计算 PWM 波各脉冲宽度和间隔,据此控制开关器件的通断,就可得到所需 PWM 波形;
调制法
拟合波形作调制信号,进行调制得到期望的 PWM 波;该方法一般采用等腰三角波为载波,其任一点水平宽度和高度成线性关系且左右对称。载波(等腰三角波)与平缓变化的调制信号波(即要拟合的波形)相交,在载波与信号波的交点控制器件通断,就得宽度正比于信号波幅值的脉冲,符合 PWM 的要求。相对于计算法,其处理过程计算简单。
二、PWM控制单色LED—单色呼吸灯
硬件说明
本文使用的是野火STM32F103指南者开发板
本开发板中设计的 RGB 灯控制引脚是经过仔细选择的,因为本实验的软件将使用STM32 的定时器控制输出 PWM 脉冲,然而并不是任意 GPIO都具有 STM32 定时器的输出通道功能,所以在设计硬件时,需要根据《STM32 中文数据手册》中的说明,选择具有定时器输出通道功能的引脚来控制 RGB 灯。
本实验中的 RGB灯使用阴极分别连接到了 PB5、PB0及 PB1,它们分别是定时器 TIM3的通道 2、3、4,其中 PB5 用于定时器输出通道时,需要使用重定义功能。
代码分析
这里用的例子是野火官方例程,可以去官网下载,在里面找到TIM—单色呼吸灯就可以了。
硬件相关宏定义文件bsp_breathing.h
#ifndef __PWM_BREATHING_H
#define __PWM_BREATHING_H
#include “stm32f10x.h”
/*PWM表中的点数*/
extern uint16_t POINT_NUM ;
//控制输出波形的频率
extern __IO uint16_t period_class ;
#define RED_LIGHT 1
#define GREEN_LIGHT 2
#define BLUE_LIGHT 3
/*要使用什么颜色的呼吸灯,可选RED_LIGHT、GREEN_LIGHT、BLUE_LIGHT*/
#define LIGHT_COLOR RED_LIGHT
/********************定时器通道**************************/
#if LIGHT_COLOR == RED_LIGHT
/************红灯***************/
#define BRE_TIMx TIM3
#define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BRE_TIM_CLK RCC_APB1Periph_TIM3
#define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO)
//红灯的引脚需要重映射
#define BRE_GPIO_REMAP_FUN() GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
#define BRE_TIM_LED_PORT GPIOB
#define BRE_TIM_LED_PIN GPIO_Pin_5
#define BRE_TIM_OCxInit TIM_OC2Init //通道选择,1~4
#define BRE_TIM_OCxPreloadConfig TIM_OC2PreloadConfig
#define BRE_CCRx CCR2
#define BRE_TIMx_IRQn TIM3_IRQn //中断
#define BRE_TIMx_IRQHandler TIM3_IRQHandler
#elif LIGHT_COLOR == GREEN_LIGHT
/************绿灯***************/
#define BRE_TIMx TIM3
#define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BRE_TIM_CLK RCC_APB1Periph_TIM3
#define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB)
//绿灯不需要重映射
#define BRE_GPIO_REMAP_FUN()
#define BRE_TIM_LED_PORT GPIOB
#define BRE_TIM_LED_PIN GPIO_Pin_0
#define BRE_TIM_OCxInit TIM_OC3Init //通道选择,1~4
#define BRE_TIM_OCxPreloadConfig TIM_OC3PreloadConfig
#define BRE_CCRx CCR3
#define BRE_TIMx_IRQn TIM3_IRQn //中断
#define BRE_TIMx_IRQHandler TIM3_IRQHandler
#elif LIGHT_COLOR == BLUE_LIGHT
/************蓝灯***************/
#define BRE_TIMx TIM3
#define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BRE_TIM_CLK RCC_APB1Periph_TIM3
#define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB)
//蓝灯不需要重映射
#define BRE_GPIO_REMAP_FUN()
#define BRE_TIM_LED_PORT GPIOB
#define BRE_TIM_LED_PIN GPIO_Pin_1
#define BRE_TIM_OCxInit TIM_OC4Init //通道选择,1~4
#define BRE_TIM_OCxPreloadConfig TIM_OC4PreloadConfig
#define BRE_CCRx CCR4
#define BRE_TIMx_IRQn TIM3_IRQn //中断
#define BRE_TIMx_IRQHandler TIM3_IRQHandler
#endif
void TIMx_Breathing_Init (void);
#endif /* __PWM_BREATHING_H */
为方便切换LED 灯 的颜色,它定义了三组宏 , 通 过 修 改 代 码 中 的 “#defineLIGHT_COLOR RED_LIGHT”语句,可以切换使用红、绿、蓝种颜色的呼吸灯。在每组宏定义中,与全彩 LED 灯实验中的类似,定义了定时器编号、定时器时钟使能库函数、引脚重映射操作、GPIO 端口和引脚号、通道对应的比较寄存器名以及中断通道和中断服务函数名。
初始化GPIO口,代码:
本实验直接使用定时器输出通道的脉冲信号控制 LED 灯,此处代码把 ==GPIO ==相关的引脚配置成了复用推挽输出模式。其中由于红灯使用的引脚需要用到第二功能,本代码使用宏 BRE_GPIO_REMAP_FUN ()进行了该引脚的功能重定义操作。
这是PWM表:
PWM 表记录了呼吸特性曲线,在本实验中,PWM 表的数据将会被赋
值到定时器的 CCRx 比较寄存器,从而控制输出占空比呈呼吸特性曲线变化的 PWM 波。
。定时器 PWM 配置
static void NVIC_Config_PWM(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置TIM3_IRQ中断为中断源 */
NVIC_InitStructure.NVIC_IRQChannel = BRE_TIMx_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/**
* @brief 配置TIM输出的PWM信号的模式,如周期、极性
* @param 无
* @retval 无
*/
static void TIMx_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
/* 设置TIM3CLK 时钟 */
BRE_TIM_APBxClock_FUN ( BRE_TIM_CLK, ENABLE );
/* 基本定时器配置 ,配合PWM表点数、中断服务函数中的period_cnt循环次数设置*/
************************************************************/
/* 基本定时器配置 */
TIM_TimeBaseStructure.TIM_Period = (1024-1);; //当定时器从0计数到 TIM_Period+1 ,为一个定时周期
TIM_TimeBaseStructure.TIM_Prescaler = (200-1); //设置预分频
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ; //设置时钟分频系数:不分频(这里用不到)
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(BRE_TIMx, &TIM_TimeBaseStructure);
/* PWM模式配置 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //配置为PWM模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能输出
TIM_OCInitStructure.TIM_Pulse = 0; //设置初始PWM脉冲宽度为0
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //当定时器计数值小于CCR1_Val时为低电平
BRE_TIM_OCxInit ( BRE_TIMx, &TIM_OCInitStructure ); //使能通道
BRE_TIM_OCxPreloadConfig ( BRE_TIMx, TIM_OCPreload_Enable ); //使能预装载
TIM_ARRPreloadConfig(BRE_TIMx, ENABLE); //使能TIM重载寄存器ARR
/* TIM3 enable counter */
TIM_Cmd(BRE_TIMx, ENABLE); //使能定时器
TIM_ITConfig(BRE_TIMx, TIM_IT_Update, ENABLE); //使能update中断
NVIC_Config_PWM(); //配置中断优先级
}
#python计算脚本 count.py
#PWM点数
POINT_NUM = 110
#周期倍数
PERIOD_CLASS = 10
#定时器定时周期
TIMER_TIM_Period = 2**10
#定时器分频
TIMER_TIM_Prescaler = 200
#STM32系统时钟频率和周期
f_pclk = 72000000
t_pclk = 1/f_pclk
#定时器update事件周期
t_timer = t_pclk*TIMER_TIM_Prescaler*TIMER_TIM_Period
#每个PWM点的时间
T_Point = t_timer * PERIOD_CLASS
#整个呼吸周期
T_Up_Down_Cycle = T_Point * POINT_NUM
print (“呼吸周期:”,T_Up_Down_Cycle)
#运行结果:
呼吸周期:3.12888
代码中初始化了控制 RGB 灯用的定时器,
它被配置为向上计数,PWM 通道输出也被配置成当计数器 CNT 的值小于输出比较寄存器CCRx的值时,PWM通道输出低电平,点亮 LED 灯。在函数的最后还使能了定时器中断,每当定时器的一个计数周期完成时,产生中断,配合中断服务函数,即可切换 CCRx 比较
寄存器的值。
代码中的 TIM_Period 和 TIM_Prescaler 是关键配置。其中 TIMPeriod 被配置为(1024-1),它控制控制定时器的定时周期,定时器的计数寄存器 CNT 从 0 开始,每个时钟会对计数器加 1,计数至 1023 时完成一次计数,产生中断,也就是说一共 1024 个计数周期,与 PWM 表元素中的最大值相同。若定时器的输出比较寄存器 CCRx被赋值为 PWM表中的元素,即可改变输出对应占空比的 PWM波,控制 LED灯。
main.c
main 函数中直接调用了 TIMx_Breathing_Init 函数,而该函数内部又直接调用了GPIO和PWM配置函数:TIMx_GPIO_Config和TIMx_Mode_Config。初始化完成后,定时器开始工作,然后它会在中断服务函数中切换 PWM 数据,控制 LED 灯显示呼吸效果。
进行验证
示波器查看波形
这里因为我们点亮的是PB5所控制的LED灯,所以我们的测试点也是PB5。
一.PWM简介
PWM是 Pulse Width Modulation 的缩写,中文意思就是脉冲宽度调 制,简称脉宽调制。 PWM是一种对模拟信号电平进行数字编码 的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个 具体模拟信号的电平进行编码。PWM 信号仍然是数字的,因为在给定的 任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压 或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去 的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被 断开的时候。只要带宽足够,任何模拟值都可以使用 PWM 进行编码。
PWM生成方法
计算法
根据拟合波形的频率、幅值和半周期脉冲数,准确计算 PWM 波各脉冲宽度和间隔,据此控制开关器件的通断,就可得到所需 PWM 波形;
调制法
拟合波形作调制信号,进行调制得到期望的 PWM 波;该方法一般采用等腰三角波为载波,其任一点水平宽度和高度成线性关系且左右对称。载波(等腰三角波)与平缓变化的调制信号波(即要拟合的波形)相交,在载波与信号波的交点控制器件通断,就得宽度正比于信号波幅值的脉冲,符合 PWM 的要求。相对于计算法,其处理过程计算简单。
二、PWM控制单色LED—单色呼吸灯
硬件说明
本文使用的是野火STM32F103指南者开发板
本开发板中设计的 RGB 灯控制引脚是经过仔细选择的,因为本实验的软件将使用STM32 的定时器控制输出 PWM 脉冲,然而并不是任意 GPIO都具有 STM32 定时器的输出通道功能,所以在设计硬件时,需要根据《STM32 中文数据手册》中的说明,选择具有定时器输出通道功能的引脚来控制 RGB 灯。
本实验中的 RGB灯使用阴极分别连接到了 PB5、PB0及 PB1,它们分别是定时器 TIM3的通道 2、3、4,其中 PB5 用于定时器输出通道时,需要使用重定义功能。
代码分析
这里用的例子是野火官方例程,可以去官网下载,在里面找到TIM—单色呼吸灯就可以了。
硬件相关宏定义文件bsp_breathing.h
#ifndef __PWM_BREATHING_H
#define __PWM_BREATHING_H
#include “stm32f10x.h”
/*PWM表中的点数*/
extern uint16_t POINT_NUM ;
//控制输出波形的频率
extern __IO uint16_t period_class ;
#define RED_LIGHT 1
#define GREEN_LIGHT 2
#define BLUE_LIGHT 3
/*要使用什么颜色的呼吸灯,可选RED_LIGHT、GREEN_LIGHT、BLUE_LIGHT*/
#define LIGHT_COLOR RED_LIGHT
/********************定时器通道**************************/
#if LIGHT_COLOR == RED_LIGHT
/************红灯***************/
#define BRE_TIMx TIM3
#define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BRE_TIM_CLK RCC_APB1Periph_TIM3
#define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO)
//红灯的引脚需要重映射
#define BRE_GPIO_REMAP_FUN() GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
#define BRE_TIM_LED_PORT GPIOB
#define BRE_TIM_LED_PIN GPIO_Pin_5
#define BRE_TIM_OCxInit TIM_OC2Init //通道选择,1~4
#define BRE_TIM_OCxPreloadConfig TIM_OC2PreloadConfig
#define BRE_CCRx CCR2
#define BRE_TIMx_IRQn TIM3_IRQn //中断
#define BRE_TIMx_IRQHandler TIM3_IRQHandler
#elif LIGHT_COLOR == GREEN_LIGHT
/************绿灯***************/
#define BRE_TIMx TIM3
#define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BRE_TIM_CLK RCC_APB1Periph_TIM3
#define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB)
//绿灯不需要重映射
#define BRE_GPIO_REMAP_FUN()
#define BRE_TIM_LED_PORT GPIOB
#define BRE_TIM_LED_PIN GPIO_Pin_0
#define BRE_TIM_OCxInit TIM_OC3Init //通道选择,1~4
#define BRE_TIM_OCxPreloadConfig TIM_OC3PreloadConfig
#define BRE_CCRx CCR3
#define BRE_TIMx_IRQn TIM3_IRQn //中断
#define BRE_TIMx_IRQHandler TIM3_IRQHandler
#elif LIGHT_COLOR == BLUE_LIGHT
/************蓝灯***************/
#define BRE_TIMx TIM3
#define BRE_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define BRE_TIM_CLK RCC_APB1Periph_TIM3
#define BRE_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define BRE_TIM_GPIO_CLK (RCC_APB2Periph_GPIOB)
//蓝灯不需要重映射
#define BRE_GPIO_REMAP_FUN()
#define BRE_TIM_LED_PORT GPIOB
#define BRE_TIM_LED_PIN GPIO_Pin_1
#define BRE_TIM_OCxInit TIM_OC4Init //通道选择,1~4
#define BRE_TIM_OCxPreloadConfig TIM_OC4PreloadConfig
#define BRE_CCRx CCR4
#define BRE_TIMx_IRQn TIM3_IRQn //中断
#define BRE_TIMx_IRQHandler TIM3_IRQHandler
#endif
void TIMx_Breathing_Init (void);
#endif /* __PWM_BREATHING_H */
为方便切换LED 灯 的颜色,它定义了三组宏 , 通 过 修 改 代 码 中 的 “#defineLIGHT_COLOR RED_LIGHT”语句,可以切换使用红、绿、蓝种颜色的呼吸灯。在每组宏定义中,与全彩 LED 灯实验中的类似,定义了定时器编号、定时器时钟使能库函数、引脚重映射操作、GPIO 端口和引脚号、通道对应的比较寄存器名以及中断通道和中断服务函数名。
初始化GPIO口,代码:
本实验直接使用定时器输出通道的脉冲信号控制 LED 灯,此处代码把 ==GPIO ==相关的引脚配置成了复用推挽输出模式。其中由于红灯使用的引脚需要用到第二功能,本代码使用宏 BRE_GPIO_REMAP_FUN ()进行了该引脚的功能重定义操作。
这是PWM表:
PWM 表记录了呼吸特性曲线,在本实验中,PWM 表的数据将会被赋
值到定时器的 CCRx 比较寄存器,从而控制输出占空比呈呼吸特性曲线变化的 PWM 波。
。定时器 PWM 配置
static void NVIC_Config_PWM(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* 配置TIM3_IRQ中断为中断源 */
NVIC_InitStructure.NVIC_IRQChannel = BRE_TIMx_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/**
* @brief 配置TIM输出的PWM信号的模式,如周期、极性
* @param 无
* @retval 无
*/
static void TIMx_Mode_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
/* 设置TIM3CLK 时钟 */
BRE_TIM_APBxClock_FUN ( BRE_TIM_CLK, ENABLE );
/* 基本定时器配置 ,配合PWM表点数、中断服务函数中的period_cnt循环次数设置*/
************************************************************/
/* 基本定时器配置 */
TIM_TimeBaseStructure.TIM_Period = (1024-1);; //当定时器从0计数到 TIM_Period+1 ,为一个定时周期
TIM_TimeBaseStructure.TIM_Prescaler = (200-1); //设置预分频
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1 ; //设置时钟分频系数:不分频(这里用不到)
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(BRE_TIMx, &TIM_TimeBaseStructure);
/* PWM模式配置 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //配置为PWM模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能输出
TIM_OCInitStructure.TIM_Pulse = 0; //设置初始PWM脉冲宽度为0
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //当定时器计数值小于CCR1_Val时为低电平
BRE_TIM_OCxInit ( BRE_TIMx, &TIM_OCInitStructure ); //使能通道
BRE_TIM_OCxPreloadConfig ( BRE_TIMx, TIM_OCPreload_Enable ); //使能预装载
TIM_ARRPreloadConfig(BRE_TIMx, ENABLE); //使能TIM重载寄存器ARR
/* TIM3 enable counter */
TIM_Cmd(BRE_TIMx, ENABLE); //使能定时器
TIM_ITConfig(BRE_TIMx, TIM_IT_Update, ENABLE); //使能update中断
NVIC_Config_PWM(); //配置中断优先级
}
#python计算脚本 count.py
#PWM点数
POINT_NUM = 110
#周期倍数
PERIOD_CLASS = 10
#定时器定时周期
TIMER_TIM_Period = 2**10
#定时器分频
TIMER_TIM_Prescaler = 200
#STM32系统时钟频率和周期
f_pclk = 72000000
t_pclk = 1/f_pclk
#定时器update事件周期
t_timer = t_pclk*TIMER_TIM_Prescaler*TIMER_TIM_Period
#每个PWM点的时间
T_Point = t_timer * PERIOD_CLASS
#整个呼吸周期
T_Up_Down_Cycle = T_Point * POINT_NUM
print (“呼吸周期:”,T_Up_Down_Cycle)
#运行结果:
呼吸周期:3.12888
代码中初始化了控制 RGB 灯用的定时器,
它被配置为向上计数,PWM 通道输出也被配置成当计数器 CNT 的值小于输出比较寄存器CCRx的值时,PWM通道输出低电平,点亮 LED 灯。在函数的最后还使能了定时器中断,每当定时器的一个计数周期完成时,产生中断,配合中断服务函数,即可切换 CCRx 比较
寄存器的值。
代码中的 TIM_Period 和 TIM_Prescaler 是关键配置。其中 TIMPeriod 被配置为(1024-1),它控制控制定时器的定时周期,定时器的计数寄存器 CNT 从 0 开始,每个时钟会对计数器加 1,计数至 1023 时完成一次计数,产生中断,也就是说一共 1024 个计数周期,与 PWM 表元素中的最大值相同。若定时器的输出比较寄存器 CCRx被赋值为 PWM表中的元素,即可改变输出对应占空比的 PWM波,控制 LED灯。
main.c
main 函数中直接调用了 TIMx_Breathing_Init 函数,而该函数内部又直接调用了GPIO和PWM配置函数:TIMx_GPIO_Config和TIMx_Mode_Config。初始化完成后,定时器开始工作,然后它会在中断服务函数中切换 PWM 数据,控制 LED 灯显示呼吸效果。
进行验证
示波器查看波形
这里因为我们点亮的是PB5所控制的LED灯,所以我们的测试点也是PB5。
举报