STM32学习:如何实现PWM输出

STM32 学习10 PWM输出

  • 一、PWM简介
  • 1. PWM的概念
  • 2. PWM的工作原理
  • 3. PWM 常用的应用场景
  • 二、一些概念
  • 1. 频率
  • 2. 占空比
  • 三、STM32F1 PWM介绍
  • 1. 定时器与寄存器
  • (1)**自动重装载寄存器(ARR)**:
  • (2)**比较寄存器(CCR)**:
  • 2. PWM的输出模式
  • (1)PWM模式1
  • (2)PWM模式2
  • 3. 边沿对齐与中心对齐
  • (1)边沿对齐模式
  • (2)中心对齐模式
  • 四、PWM输出配置步骤
  • 1. 使能定时器及端口时钟
  • 2. 定时器的重映像
  • (1)定时器4复用功能重映像
  • (2)定时器3复用功能重映像
  • (3) 定时器2复用功能重映像
  • 3. 输出端口复用
  • 4. 初始化定时器参数
  • 5. 初始化PWM输出参数
  • 6. 开启定时器
  • 7. 修改TIMx_CCRx的值控制占空比
  • 8. 使能 TIMx 在 CCRx 上的预装载寄存器
  • 9. 使能 TIMx 在 ARR上的预装载寄存器允许位
  • 10. 设置 MOE位
  • 五、代码示例
  • 1. pwm_utils.h
  • 2. pwm_utils.c
  • 3. main函数实现
  • 一、PWM简介

    1. PWM的概念

    PWM的全称是脉冲宽度调制(Pulse Width Modulation),是一种控制模拟信号的方法。它通过改变脉冲的宽度来控制模拟信号的平均值。

    2. PWM的工作原理

    PWM的工作原理是将一个周期性的脉冲信号与一个控制信号进行比较。当控制信号大于脉冲信号时,输出高电平;当控制信号小于脉冲信号时,输出低电平。通过改变脉冲信号的宽度,可以控制输出信号的平均值。

    输出信号的平均值连在一起,可以达到模拟信号的效果,如下图所示:
    wikipedia示例

    3. PWM 常用的应用场景

  • 电机控制:用于控制电机的速度和方向;
  • 照明控制:用于控制灯光的亮度;
  • 电源管理:用于控制电源的输出电压;
  • 音频控制:用于控制声音的大小。
  • 二、一些概念

    1. 频率

    PWM波形在单位时间内重复出现的次数。

    2. 占空比

    PWM波形中高电平信号所占的比例。

    三、STM32F1 PWM介绍

    1. 定时器与寄存器

    STM32F1除了基本定时器TIM6和TIM7,其它定时器都可以产生PWM输出。其中:

  • TIM1和TIM8:均可同时产生7路PWM输出;
  • 其它通用定时器:均可同时产生4路PWM输出。
  • 在STM32微控制器中,生成PWM信号通常涉及到自动重装载寄存器(ARR)和比较寄存器(CCR)两个重要的寄存器。

    (1)自动重装载寄存器(ARR)

  • 通过修改ARR的值,可以调节PWM信号的周期,从而改变PWM信号的频率。
  • 当ARR增加时,整个PWM信号的周期增加,导致PWM信号的频率降低。
  • (2)比较寄存器(CCR)

  • 通过修改CCR的值,可以调节PWM信号的占空比,从而改变PWM信号的高电平持续时间。
  • CCR的值通常应该小于ARR的值,以确保PWM信号的占空比在0到100%之间。
  • 当CCR增加时,高电平部分的持续时间增加,导致PWM信号的占空比增加。
  • 2. PWM的输出模式

    PWM输出模式一共8种,常用的是PWM1和PWM2,其用法差不多,区别如下:
    下表是PWM1和PWM2的区别:

    (1)PWM模式1

    在该模式下,定时器的计数器从0开始递增,

  • 当计数器的值小于CCR时,输出为高电平;
  • 当计数器的值大于等于CCR时,输出为低电平;
  • 在计数器达到ARR时,产生一个更新事件,计数器重新从0开始计数。
  • 这种模式下,PWM信号的周期由ARR决定,占空比由CCR决定。

    (2)PWM模式2

    与PWM模式1相比,PWM模式2输出有效性正好是相反的。

    下表是PWM1和PWM2的比较:

    模式 CNT 计算方式 CNT<CCR CNT>CCR
    PWM1 递增 通道CH有效 通道CH无效
    PWM1 递减 通道CH无效 通道CH有效
    PWM2 递增 通道CH无效 通道CH有效
    PWM2 递减 通道CH有效 通道CH无效

    3. 边沿对齐与中心对齐

    (1)边沿对齐模式

  • 在边沿对齐模式下,PWM信号的起始位置位于PWM周期的起始边沿(即ARR),然后递增至CCR,再递增至ARR,最后重复此过程。
  • PWM信号的高电平和低电平都与PWM周期的边沿对齐,即从PWM周期的起始边沿开始。
  • 边沿对齐模式通常用于需要高精度输出的应用,例如需要精确控制PWM信号的起始和终止时间的应用场景。

    以上图为例,TIMx_CR1寄存器的DIR位为低时,递增计数,设ARR=8,当CCRx=4时:
  • CNT从0增至3的时候,输出PWM参考信号0CxREF为有效的高电平;
  • CNT从4到8的时候,0CxREF输出为低电平;
  • 0CXREF表示定时器的比较器

    (2)中心对齐模式

  • 在中心对齐模式下,PWM信号的起始位置位于PWM周期的中间,然后递增至CCR,再递减至0,再重复此过程。
  • PWM信号的高电平和低电平都与PWM周期的中心对齐,即从PWM周期的中间开始。
  • 中心对齐模式通常用于需要调节占空比范围较大的应用,例如需要在PWM周期内任意调节占空比的应用场景。由于PWM信号的起始位置位于PWM周期的中间,因此可以实现更宽范围的占空比调节。

  • 以上图为例,设ARR=8,当CCRx=4时,

  • 当CNT<CCRx,输出为有效信号高电平 ;
  • 当CNT>CCRx,输出为有效信号低电平;
  • 四、PWM输出配置步骤

    PWM 的配置在库文件 time.c 中。

    1. 使能定时器及端口时钟

    下面是使能设置代码:

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    
    

    2. 定时器的重映像

    后面示例的 PWM 需要配置引脚的复用功能(重映像),定时器的重映像可在《STM32F10x参考手册》查询,摘录如下:

    (1)定时器4复用功能重映像

    (2)定时器3复用功能重映像

    (3) 定时器2复用功能重映像

    以使用 TIM3 的通道1为例,它默认是在PA6引脚上,它完全重映像是在PC6,后面使用的开发板上原理图示:

    示例代码将使用PC6输出TIM3的通道1 PWM波。

    代码示例:

    // 设置 TIM3 完全重映像
    GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);
    

    3. 输出端口复用

    在输出PWM信号时,通常需要考虑信号的稳定性、噪声抑制以及输出电流的能力等因素。复用推挽输出是一种常见的配置方式。

    // 复用推挽输出
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;  
    

    4. 初始化定时器参数

    包括 : 自动重载值、分频系数、计数方式等。

    void TIM_TimeBaseInit(TIM_TypeDef*TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStructure)
    

    5. 初始化PWM输出参数

    包括 :PWM 模式、输出极性、使能等。

    void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStructure);
    
    // 结构体定义 
    typedef struct
    {
    	uint16_t TIM_OCMode;  		// 比较输出模式
    	uint16_t TIM_OutputState;  	// 比较输出使能
    	uint16_t TIME_OutputNState: // 比较互补输出使能
    	uint32_t TIM_Pulse;        	// 脉冲宽度 0~65535
      /**
       * 输出极性
       *   * TIM_OCPolarity_High: 高电平有效
       *   * TIM_OCPolarity_Low: 低电平有效
       */
    	uint16_t TIM_OCPolarity;
      /**
       * 互补比较输出极性
       *   * TIM_OCNPolarity_High: 高电平有效
       *   * TIM_OCNPolarity_Low: 低电平有效
       */	
    	uint16_t TIM_OCNPolarity;
      /**
       * 空闲状态下比较输出状态
       *   * TIM_OCIdleState_Set: 置位
       *   * TIM_OCIdleState_Reset: 复位
       */	
    	uint16_t TIM_OCIdleState;
      /**
       * 空闲状态下比较输出状态
       *   * TIM_OCNIdleState_Set: 置位
       *   * TIM_OCNIdleState_Reset: 复位
       */
    	uint16_t TIM_OCNIdleState;
    } TIM_OCInitTypeDef;
    
    

    6. 开启定时器

    // NewState: 新的状态,可以是 ENABLE 或 DISABLE。
    void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)   
    

    7. 修改TIMx_CCRx的值控制占空比

    void TIM_SetCompare1(TIM_TypeDef* TIMx, uint32_t Compare1);
    

    8. 使能 TIMx 在 CCRx 上的预装载寄存器

    // 参数 TIM_OCPreload 可为 TIM_OCPreload_Enable、TIM_OCPreload_Disable
    void TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
    

    9. 使能 TIMx 在 ARR上的预装载寄存器允许位

    // NewState: 新的状态,可以是 ENABLE 或 DISABLE。
    void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
    

    10. 设置 MOE位

    对于高级定时器,需要设置MOE位。
    MOE 位,全称 Master Output Enable,是定时器控制寄存器 1 (TIMx->CR1) 中的一个控制位(15位),用于使能或禁用定时器主输出。

  • MOE 位可以用于控制 PWM 输出的使能和禁用。
  • 可以使用 MOE 位来实现软启动和软停止功能。
  • 可以使用 MOE 位来实现故障保护功能。
  • void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);
    

    五、代码示例

    本实验对TIM3控制,使用通道1, 对TIM3_CH1重映像到PC6引脚,控制PC6上接的LED亮度。
    示例程序控制LED呼吸灯效果,渐渐变亮,再渐渐变暗。

    1. pwm_utils.h

    #ifndef __PWM_UTILS_H__
    #define __PWM_UTILS_H__
    
    #include "stm32f10x.h"
    
    void tim3_ch1_pwm_init(u16 preriod, u16 prescaler);
    void tim3_ch1_pwm_set_duty(u16 duty);
    #endif
    
    

    2. pwm_utils.c

    #include "pwm_utils.h"
    #include "led_utils.h"
    
    /**
     * @brief  定时器3初始化
    */
    void tim3_ch1_pwm_init(u16 preriod, u16 prescaler){
        // 使能TIM3时钟
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
        // 使能LED所在端口的时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
        // 使能AFIO
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    
        GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO初始化结构体
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置输出速度为50MHz
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置为推挽输出模式
        GPIO_Init(LED_PORT, &GPIO_InitStructure); //初始化 LED_PORT
    
        // 管脚重映像
        GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);
        // 定时器初始化
        TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
        TIM_TimeBaseStructure.TIM_Period = preriod; //设置自动重装载寄存器周期值
        TIM_TimeBaseStructure.TIM_Prescaler = prescaler; //设置时钟预分频数
        TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分频因子
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
        // 初始化
        TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
        // PWM模式1
        TIM_OCInitTypeDef TIM_OCInitStructure;
        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
        TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性高
        TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
        TIM_OC1Init(TIM3, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC1
    
        // 使能TIM3的CCR1寄存器预装载
        TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
        // 使能TIM3的ARR寄存器预装载
        TIM_ARRPreloadConfig(TIM3, ENABLE);
        // 使能TIM3
        TIM_Cmd(TIM3, ENABLE);
    }
    void tim3_ch1_pwm_set_duty(u16 duty){
        // 设置定时器3的PWM占空比
        TIM_SetCompare1(TIM3, duty);
    }
    
    

    3. main函数实现

    #include "gpio_utils.h"
    #include "stm32f10x.h"
    #include "sys_tick_utils.h"
    #include "led_utils.h"
    #include "pwm_utils.h"
    
    // 主函数
    int main(void)
    {
    	// led 初始化
        custom_led_init();
    	// tick 初始化
    	sys_tick_init(72);
    
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    	// PWM 初始化,2K
    	tim3_ch1_pwm_init(500, 72-1);
    
    	led_all_off();
    	
    	int i = 0;
    	u8 direction=0;
        while (1) //无限循环
        {
    		tim3_ch1_pwm_set_duty(i);
    		if(direction==0){
    			i++;
    		}else{
    			i--;
    		}
    		if(i>300){
    			direction = 1;
    		}else if(i<1){
    			direction = 0;
    		}
    		delay_ms(10);
        }
    }
    
    

    实测PC6的波形是一直变化中:


    本文代码开源地址:
    https://gitee.com/xundh/stm32_arm_learn

    作者:编程圈子

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32学习:如何实现PWM输出

    发表回复