脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用
    微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽
    度的控制。
    STM32 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。其中高级定
    时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4
    路的 PWM 输出,这样,STM32 最多可以同时产生 30 路 PWM 输出!

    公式为:溢出时间Tout=(arr+1)(psc+1)/Tclk
    Tclk为通用定时器的时钟,如果APB1没有分频,则就为系统时钟,72MHZ
    如:TIM3_PWM_Init(899,0);
    //PWM时钟频率=72000000/(899+1) = 80KHZ

    1. __IO u16 CCR1_Val=40;//72000000/36/40=50KHZ
    2. __IO u16 CCR2_Val=20000;//72000000/36/20000=100HZ
    3. float zhankong1=0.5;
    4. float zhankong2=0.5;
    5. u16 capture=0;
    6. u8 pa6_state=0,pa7_state;
    7. //比较输出PWM配置
    8. void TIM3_PWM_Init2(void)
    9. {
    10. NVIC_InitTypeDef NVIC_InitStructure;
    11. GPIO_InitTypeDef GPIO_InitStructure;
    12. TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    13. TIM_OCInitTypeDef TIM_OCInitStructure;
    14. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    15. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
    16. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    17. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    18. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    19. GPIO_Init(GPIOA, &GPIO_InitStructure);//IO口配置
    20. NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
    21. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    22. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
    23. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    24. NVIC_Init(&NVIC_InitStructure);//TIM3中断配置
    25. TIM_TimeBaseStructure.TIM_Period = 39999;
    26. TIM_TimeBaseStructure.TIM_Prescaler = 35;
    27. TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    28. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    29. TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);//定时器基本配置
    30. /* Output Compare Toggle Mode configuration: Channel1 */
    31. TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
    32. TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    33. TIM_OCInitStructure.TIM_Pulse = CCR1_Val;
    34. TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
    35. TIM_OC1Init(TIM3, &TIM_OCInitStructure);//通道1设置为输出比较模式
    36. TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Disable);
    37. /* Output Compare Toggle Mode configuration: Channel2 */
    38. TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    39. TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
    40. TIM_OC2Init(TIM3, &TIM_OCInitStructure);//通道2也设置为输出比较模式
    41. TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Disable);//使能预装载
    42. /* TIM enable counter */
    43. TIM_Cmd(TIM3, ENABLE);//定时器3使能
    44. /* TIM IT enable */
    45. TIM_ITConfig(TIM3, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);//通道1、通道2中断使能
    46. }
    47. void TIM3_IRQHandler(void)
    48. {
    49. /* TIM3_CH1 toggling with frequency = 50KHz */
    50. if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
    51. {
    52. TIM_ClearITPendingBit(TIM3, TIM_IT_CC1 );
    53. capture = TIM_GetCapture1(TIM3);
    54. if(pa6_state==0)
    55. {
    56. TIM_SetCompare1(TIM3, capture + (u16)CCR1_Val *zhankong1 );
    57. pa6_state=1;
    58. }
    59. else
    60. {
    61. TIM_SetCompare1(TIM3, capture + (u16)CCR1_Val *(1-zhankong1) );
    62. pa6_state=0;
    63. }
    64. }
    65. /* TIM3_CH2 toggling with frequency = 100 Hz */
    66. if (TIM_GetITStatus(TIM3, TIM_IT_CC2) != RESET)
    67. {
    68. TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);
    69. capture = TIM_GetCapture2(TIM3);
    70. if(pa7_state==0)
    71. {
    72. TIM_SetCompare2(TIM3, capture + (u16)CCR2_Val *zhankong2 );
    73. pa7_state=1;
    74. }
    75. else
    76. {
    77. TIM_SetCompare2(TIM3, capture + (u16)CCR2_Val *(1-zhankong2) );
    78. pa7_state=0;
    79. }
    80. }
    81. }
    1. 首先配置IO口,这个就是TIM3的通道1、通道2对应的IO口,分别是PA6PA7
    2. 接下来我们设置了定时器3的中断以及定时器的一些基本配置,比如预分频值、重加载值等,我们这里设置预分频值为35(实际会+136),重装载值为39999(实际会+140000
    3. 然后配置通道1和通道2,配置为输出比较模式,使能输出,设置比较值为CCR1_Val,设置有效电平为低电平,同样通道2也是如此的配置
    4. 最后我们使能预装载,使能定时器,使能中断
    5. 接下来就到了输出比较模式的核心----中断服务函数了,中断服务函数中的处理是十分关键的,当有中断触发(CNT寄存器的值累加到了某个通道的比较值时),就会触发中断,根据中断的标志来判断是哪个通道触发了中断,紧接着它会查看当前CNT寄存器的值(使用TIM_GetCapturex函数),然后它判断现在是高电平还是低电平(这个是由有效电平控制,并有state标志位来判断),由此来设置新的比较值(使用TIM_SetComparex函数),这样就可以连续的生成固定频率固定占空比的PWM
    6. 有几个需要注意的点:因为CNT的值是从0计数到ARR寄存器值的,而我们每次会设置CCRx_Val这个值,所以说ARR寄存器的值最好是你每个通道CCRx_Val的公倍数,否则在每次重加载的时候波形会发生混乱。我们可以通过改变CCRx_Val来改变PWM的频率,通过改变zhankongx来改变PWM的占空比,这两个值是随时都可以修改的。
    7. 总结:<br /> 使用输出比较的方法可以在使用1个定时器的情况下有效的生成两路不同频率及占空比的PWM,它对比PWM输出模式的缺点肯定就是它会有中断的处理,如果生成的PWM频率较高时它会频繁的进入比较中断,这可能会给单片机带来较大的负担,但是在输出较低频率的PWM时,这种方法还是很好用的。