PWM有三个关键指标: PWM频率, 占空比, 区分度
对于同一个时钟频率下工作的单片机, 区分度是和PWM工作频率相关的, 因为总频率是固定的, PWM工作频率越高, 留下给区分度的部分就越低, 因此区分度就越低. 对于STM32, 如果时钟是72MHz, 在PWM频率为1KHz时, 区分度为16bit, 在281KHz时, 为8bit, 在4.5MHz时, 就时4bit了.
STM32的PWM功能是定时器功能的一部分, STM32F4系列完整的定时器是14个
| Timer | Type | Resolution | Prescaler | Channels | MAX INTERFACE CLOCK | MAX TIMER CLOCK* | APB | 
|---|---|---|---|---|---|---|---|
| TIM1, TIM8 | Advanced | 16bit | 16bit | 4 | SysClk/2 | SysClk | 2 | 
| TIM2, TIM5 | General purpose | 32bit | 16bit | 4 | SysClk/4 | SysClk, SysClk/2 | 1 | 
| TIM3, TIM4 | General purpose | 16bit | 16bit | 4 | SysClk/4 | SysClk, SysClk/2 | 1 | 
| TIM9 | General purpose | 16bit | 16bit | 2 | SysClk/2 | SysClk | 2 | 
| TIM10, TIM11 | General purpose | 16bit | 16bit | 1 | SysClk/2 | SysClk | 2 | 
| TIM12 | General purpose | 16bit | 16bit | 2 | SysClk/4 | SysClk, SysClk/2 | 1 | 
| TIM13, TIM14 | General purpose | 16bit | 16bit | 1 | SysClk/4 | SysClk, SysClk/2 | 1 | 
| TIM6, TIM7 | Basic | 16bit | 16bit | 0 | SysClk/4 | SysClk, SysClk/2 | 1 | 
F401属于低端系列, 定时器只有一部分, 内置的定时器为
每个定时器都有对应的通道数, 一般都有CH1 - CH4, 对于TIM1, 还有CH1N - CH4N
关于CH1和CH1N
后者输出相对于前者反相的PWM信号, CH1和CH1N两个通道互补输出. 在设置这两个通道输出的时候如果开启了互补输出, 那么这两个引脚的输出电平始终相反, 也就是一个引脚输出低电平, 另一个引脚自动输出高电平, 反之亦然. 这样的输出方式一般用于电机驱动控制.
| TIM1 | TIM2 | TIM3 | TIM9 | |
|---|---|---|---|---|
| CH1 | PA8 | PA0 PA5 PA15 | PA6 PB4 | PA2 | 
| CH2 | PA9 | PA1 PB3 | PA7 PB5 | PA3 | 
| CH3 | PA10 | PA2 PB10 | PB0 | |
| CH4 | PA11 | PA3 PB11 | PB1 | |
| CH1N | PB13 PA7 | |||
| CH2N | PB14 PB0 | |||
| CH3N | PB15 PB1 | 
PWM输出模式的配置主要有两个
1. TIM_OCMode: TIM输出比较和PWM模式
2. TIM_OCPolarity: PWM的有效电平
与TIM_OCMode_PWM1和TIM_OCMode_PWM2配合, TIM_OCPolarity_High表示有效电平是高电平, TIM_OCPolarity_Low是低电平
上面两个配置结合产生的效果
设置PWM频率, 即设置PWM完整周期的时钟计数次数. 这个是通过TIM_BaseStruct.TIM_Period(ARR寄存器)设置的, 要设置这个值, 首先你要知道这个值的上限, 即定时器的最大值, 例如 16bit 即 65535, 要计算出PWM频率, 可以这样计算
PWM_frequency = timer_tick_frequency / (TIM_Period + 1)
也可以通过PWM频率倒推时钟周期计数值
TIM_Period = timer_tick_frequency / PWM_frequency - 1
例如, 如果需要的PWM频率为10KHz, 则时钟的周期计数值为
TIM_Period = (84000000 / 10000) - 1; // 8399
如果需要17.57 Khz, 就是
TIM_Period = (SystemCoreClock / 17570 ) - 1;
如果通过这个式子算出来的计数值大于定时器长度(例如超过了65535), 你需要增大 prescaler, 降低系统时钟频率
如果需要在运行时修改, 可以使用函数TIM_PrescalerConfig(TIM2, 35999, TIM_PSCReloadMode_Immediate), 这个函数的作用就是在定时器工作时改变预分频器的值.
设置占空比, 需要通过设置 TIM_Pulse 参数, 这个值就是用于比较的触发值CRR, TIM_OCInitStructure.TIM_Pulse = 100表示触发值为100, 这个值的计算要结合PWM周期总计数值TIM_Period和需要的占空比百分比, 例如
pulse_length = ((TIM_Period + 1) * DutyCycle) / 100 - 1
# 其中DutyCycle是一个百分比, 例如对于TIM_Period为8399, 如果需要25%占空比
pulse_length = (8399 + 1) * 0.25 - 1 = 2099
如果需要在运行时修改, 你可以:
TIM_SetCompare[x](TIMx, Compare1)这个函数,修改CCR的值,改变输出占空比, 例如TIM_SetCompare1函数名中的数字1代表的是TIMx的通道1, 参数TIMx可以是TIM1, TIM2等, 第二个参数 Compare1, 是用于与TIMx计数值比较的数, 在TIMx达到这个计数值时将根据当前的模式和极性, 进行电平变换. TIM_SetCompareX这个函数有四个, 分别是TIM_SetCompare1, TIM_SetCompare2, TIM_SetCompare3, TIM_SetCompare4. 对应不同的CHx使用, 例如TIMx_CH1使用 TIM_SetCompare1, TIMx_CH2使用TIM_SetCompare2, 等等.void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1) {
	/* Check the parameters */
	assert_param(IS_TIM_LIST8_PERIPH(TIMx));
	/* Set the Capture Compare1 Register value */
	TIMx->CCR1 = Compare1;
}
启动对应输出口的定时器, 这里是TIM4
void TM_TIMER_Init(void) {
    TIM_TimeBaseInitTypeDef TIM_BaseStruct;
    
    /* 开启TIM4时钟 */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
    /*    
    TIM4连接的是 APB1 总线, 在F407上时钟是 42MHz, 但是有内部PLL, 将频率翻倍为 84MHz. 注意: 也有定时器是接在 APB2 总线上的, 默认工作在 84MHz, 通过内部PLL翻倍至 168MHz                                                             
    设置预分频 timer prescaller 
    时钟被设置为     timer_tick_frequency = Timer_default_frequency / (prescaller_set + 1)
    在这个例子中, 我们希望使用最大频率, 所以 prescaller 设置为 0, 所以时钟与总线时钟一致, 频率为
    timer_tick_frequency = 84000000 / (0 + 1) = 84000000 
    */    
    TIM_BaseStruct.TIM_Prescaler = 0;
    /* 使用上升沿计数 */
    TIM_BaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
    /*
    设置一个PWM完整周期的时钟计数次数, 首先你要知道定时器的最大值, 在这个例子中是16bit, 即 65535, 要计算出你的PWM频率, 可以这样计算
    PWM_frequency = timer_tick_frequency / (TIM_Period + 1)
    通过这个算式也可以通过PWM频率倒推时钟周期计数值
    TIM_Period = timer_tick_frequency / PWM_frequency - 1
    在这个例子中, 如果需要的PWM频率为10KHz, 则时钟的周期计数值为
    TIM_Period = 84000000 / 10000 - 1 = 8399
    如果通过这个式子算出来的计数值大于定时器长度(例如超过了65535), 你需要增大 prescaler 降低系统时钟频率
    */
    TIM_BaseStruct.TIM_Period = 8399; /* 10kHz PWM */
    /* TIM_ClockDivision的设置不影响PWM频率 */
    TIM_BaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_BaseStruct.TIM_RepetitionCounter = 0;
    /* TIM4 初始化 */
    TIM_TimeBaseInit(TIM4, &TIM_BaseStruct);
    /* TIM4 开始计数 */
    TIM_Cmd(TIM4, ENABLE);
}
初始化PWM 4个通道
void TM_PWM_Init(void) {
    TIM_OCInitTypeDef TIM_OCStruct;
    
    /* 通道的公用配置 */
    
    /* PWM 模式 2 = Clear on compare match 达到预设值时拉低电平 */
    /* PWM 模式 1 = Set on compare match 达到预设值时拉高电平 */
    TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM2;
    TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_Low;
    /*
    要得到期望的占空比(DutyCycle, 一个百分比), 通过这个式子计算定时器触发值
    pulse_length = ((TIM_Period + 1) * DutyCycle) / 100 - 1
    例如
    25%  占空比: pulse_length = ((8399 + 1) * 25) / 100 - 1 = 2099
    50%  占空比: pulse_length = ((8399 + 1) * 50) / 100 - 1 = 4199
    75%  占空比: pulse_length = ((8399 + 1) * 75) / 100 - 1 = 6299
    100% 占空比: pulse_length = ((8399 + 1) * 100) / 100 - 1 = 8399
    
    注意: 如果触发值大于时钟周期总长度 TIM_Period, 这个PWM将一直输出同样的电平
    */
    TIM_OCStruct.TIM_Pulse = 2099; /* 25% duty cycle */
    TIM_OC1Init(TIM4, &TIM_OCStruct);
    TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
    
    TIM_OCStruct.TIM_Pulse = 4199; /* 50% duty cycle */
    TIM_OC2Init(TIM4, &TIM_OCStruct);
    TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);
    
    TIM_OCStruct.TIM_Pulse = 6299; /* 75% duty cycle */
    TIM_OC3Init(TIM4, &TIM_OCStruct);
    TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);
    
    TIM_OCStruct.TIM_Pulse = 8399; /* 100% duty cycle */
    TIM_OC4Init(TIM4, &TIM_OCStruct);
    TIM_OC4PreloadConfig(TIM4, TIM_OCPreload_Enable);
}
初始化GPIO输出
void TM_LEDS_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    
    /* GPIOD 时钟 */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
    /* 设置这些PIN脚的功能复用 */
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_TIM4);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource13, GPIO_AF_TIM4);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_TIM4);
    GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_TIM4);
    
    /* 设置PIN脚 */
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GPIOD, &GPIO_InitStruct);
}
完整的代码
#include "defines.h"
#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_tim.h"
TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
TIM_OCInitTypeDef  TIM_OCInitStructure;
u16 TimerPeriod = 0;
u16 Channel1Pulse = 0, Channel2Pulse = 0, Channel3Pulse = 0, Channel4Pulse = 0;
void DecreasePuls() {
  Channel1Pulse = (Channel1Pulse <= 10)? TimerPeriod : Channel1Pulse - 10;
  Channel2Pulse = (Channel2Pulse <= 10)? TimerPeriod : Channel2Pulse - 10;
  Channel3Pulse = (Channel3Pulse <= 10)? TimerPeriod : Channel3Pulse - 10;
  Channel4Pulse = (Channel4Pulse <= 10)? TimerPeriod : Channel4Pulse - 10;
}
void TIM_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  /* GPIOA, GPIOB Clocks enable */
  RCC_AHB1PeriphClockCmd( RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB , ENABLE);
  
  /* GPIOA Configuration: Channel 1, 2, 3, 4 as alternate function push-pull */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
   
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM2);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_TIM2);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_TIM2);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_TIM2);
}
int main(void)
{
  Systick_Init();
  USART1_Init();
  TIM_Config();
  /* Compute the value to be set in ARR register to generate signal frequency at 17.57 Khz */
  TimerPeriod = (SystemCoreClock / 17570 ) - 1;
  /* Compute CCR1 value to generate a duty cycle at 100% for channel 1 and 1N */
  Channel1Pulse = (u16) (((u32) 10 * (TimerPeriod - 1)) / 10);
  /* Compute CCR2 value to generate a duty cycle at 30%  for channel 2 and 2N */
  Channel2Pulse = (u16) (((u32) 300 * (TimerPeriod - 1)) / 1000);
  /* Compute CCR3 value to generate a duty cycle at 20%  for channel 3 and 3N */
  Channel3Pulse = (u16) (((u32) 20 * (TimerPeriod - 1)) / 100);
  /* Compute CCR4 value to generate a duty cycle at 10%  for channel 4 */
  Channel4Pulse = (u16) (((u32) 100 * (TimerPeriod- 1)) / 1000);
  /* TIM2 clock enable */
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
  
  /* Time Base configuration */
  TIM_TimeBaseStructure.TIM_Prescaler = 0;
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  TIM_TimeBaseStructure.TIM_Period = TimerPeriod;
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  //TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
  TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
  /* Channel 1, 2,3 and 4 Configuration in PWM mode */
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM: trigger from valid -> invalid
  // TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
  //TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
  // Specifies the TIM Output Compare pin state during Idle state, ## valid only for TIM1 and TIM8 ##
  //TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
  //TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;
  //Specifies the TIM Output Compare state
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  TIM_OCInitStructure.TIM_Pulse = Channel1Pulse;
  TIM_OC1Init(TIM2, &TIM_OCInitStructure);
  // TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
  TIM_OCInitStructure.TIM_Pulse = Channel2Pulse;
  TIM_OC2Init(TIM2, &TIM_OCInitStructure);
  // TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
  TIM_OCInitStructure.TIM_Pulse = Channel3Pulse;
  TIM_OC3Init(TIM2, &TIM_OCInitStructure);
  // Enables or disables the TIMx peripheral Preload register on CCR1
  // TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable);
  TIM_OCInitStructure.TIM_Pulse = Channel4Pulse;
  TIM_OC4Init(TIM2, &TIM_OCInitStructure);
  // TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable);
  // TIM_ARRPreloadConfig(TIM2, ENABLE);
  /* TIM2 counter enable */
  TIM_Cmd(TIM2, ENABLE);
  /* TIM2 Main Output Enable */
  TIM_CtrlPWMOutputs(TIM2, ENABLE); // Enables or disables the TIM peripheral Main Outputs. 
  while (1)
  {
    printf("TP:%d, CH1:%d, CH2:%d, CH3:%d, CH4:%d\r\n", TimerPeriod, Channel1Pulse, Channel2Pulse, Channel3Pulse, Channel4Pulse);
    DecreasePuls();
    TIM_SetCompare1(TIM2, Channel1Pulse);
    TIM_SetCompare2(TIM2, Channel2Pulse);
    TIM_SetCompare3(TIM2, Channel3Pulse);
    TIM_SetCompare4(TIM2, Channel4Pulse);
    Systick_Delay_ms(100);
  }
代码中的几个函数的说明
TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable);
使能TIM2 在 CCR4 上的预装载寄存器, 即 TIM2_CCR4 的预装载值在更新事件到来时才能被传送至当前寄存器中. 就是设置 CCR4 中的预装载值何时被传送到当前的CNT寄存器中, 设置为ENABLE, 表示仅当更新事件到来的时候才装载, 追踪寄存器的设置可知, 原来设置的是CCMR1的OC2PE, 还有一种方式是立即装载. OC1PE:输出比较1预装载使能(Output compare 1 preload enable)位3
TIM_ARRPreloadConfig的作用, 是允许或禁止在定时器工作时向ARR的缓冲器中写入新值, 以便在更新事件发生时载入覆盖以前的值. 如果在初始化的时候设置了ARR的值TIM_TimeBaseStructure.TIM_Period=2000;, 后来也没更改(没有编写中断服务函数或者在中断服务函数中没有给ARR缓冲器重新写入新值), 那么设置为DISABLE和ENABLE都没有影响.
原文:https://www.cnblogs.com/milton/p/15028413.html