一.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。
结果: