正点原子STM32HAL库学习实践:stm32f103zet6通用定时器PWM输出实现呼吸灯效果实验

文章目录

  • 前言
  • 通用定时器输出PWM原理
  • 寄存器以及配置
  • 捕获/比较模式寄存器 1/2(TIMx_CCMR1/2)
  • 捕获/比较使能寄存器(TIMx_CCER)
  • 捕获/比较寄存器 1/2/3/4(TIMx_CCR1/2/3/4)
  • 硬件设计
  • 实现功能
  • 硬件资源
  • 程序设计
  • 涉及的HAL库函数
  • 1. HAL_TIM_PWM_Init 函数
  • 2. HAL_TIM_PWM_ConfigChannel 函数
  • 3. HAL_TIM_PWM_Start 函数
  • 4. HAL_TIM_ConfigClockSource 函数
  • 配置步骤
  • 代码实现
  • 前言

    PWM是什么:脉宽调制,PWM(Pulse Width Modulation),通过对一系列脉冲的宽度进行调制,来等效地获得所需要波形(含形状和幅值)。
    本次实验涉及到通用定时器框图中的⑤和⑥部分,具体可以点击传送门👉🕳👈

    通用定时器输出PWM原理

    我们可以让定时器产生PWM,在计数器频率固定时, PWM 频率或者周期由自动重载寄存器(TIMx_ARR)的值决定,其占空
    比由捕获/比较寄存器(TIMx_CCRx)的值决定
    。 PWM 产生原理示意图如下图所示:

    上图中,定时器工作在递增计数模式,纵轴是计数器的计数值 CNT,横轴表示时间。当CNT<CCRx 时IO 输出低电平(逻辑 0)当 CNT>=CCRx 时IO 输出高电平(逻辑 1);当CNT=ARR 时,定时器溢出, CNT 的值被清零,然后继续递增,依次循环。在这个循环中,改变 CCRx 的值,就可以改变 PWM 的占空比,改变 ARR 的值,就可以改变 PWM 的频率,这就是 PWM 输出的原理

    定时器产生PWM有多种,如👇图所示:

    有/无效状态由TIMx_CCER决定CCxP=0:OCx高电平有效CCxP=1:Ocx低电平有效

    寄存器以及配置

    除了之前涉及的寄存器(基本定时器👉🕳👈和通用定时器介绍👉🕳👈),这里还用到了其他的三个,来控制 PWM。这三个寄存器分别是:捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。

    捕获/比较模式寄存器 1/2(TIMx_CCMR1/2)

    该寄存器一般有 2 个:TIMx _CCMR1 和 TIMx _CCMR2。 TIMx_CCMR1 控制 CH1 和 CH2,而 TIMx_CCMR2 控制CH3 和 CH4。

    TIMx _CCMR1寄存器描述如下:

    该寄存器的有些位在不同模式下,功能不一样,我们现在只用到输出比较,输入捕获后面的实验再讲解。关于该寄存器的详细说明,请参考《STM32F10xxx 参考手册_V10(中文版) .pdf》第 288 页, 14.4.7 节。比如我们要让 TIM3 的 CH2 输出 PWM 波为例进行介绍,该寄存器的模式设置位 OC2M[2:0]就是对应着通道 2 的模式设置,此部分由 3 位组成。总共可以配置成 8 种模式,我们使用的是 PWM 模式,所以这 3 位必须设置为 110 或者 111,分别对应 PWM 模式 1和 PWM 模式 2。这两种 PWM 模式的区别就是输出有效电平的极性相反。 位 OC2PE 控制输出比较通道 2 的预装载使能,实际就是控制 CCR2 寄存器是否进行缓冲。因为 CCR2 寄存器也是有影子寄存器的,影子寄存器才是真正起作用的寄存器。 CC2S[1:0]用于设置通道 2 的方向(输入/输出)默认设置为 0,就是设置通道作为输出使用。

    捕获/比较使能寄存器(TIMx_CCER)

    该寄存器控制着各个输入输出通道的开关和极性。 TIMx_CCER 寄存器描述如图:

    该寄存器比较简单,要让 TIM3 的 CH2 输出 PWM 波,这里我们要使能 CC2E 位,该位是通道 2 输入/输出使能位,要想 PWM 从 IO 口输出,这个位必须设置为 1。 CC2P 位是设置通道2 的输出极性,我们默认设置 0。

    捕获/比较寄存器 1/2/3/4(TIMx_CCR1/2/3/4)

    捕获/比较寄存器(TIMx_CCR1/2/3/4),该寄存器总共有 4 个,对应 4 个通道 CH1~CH4。我们使用的是通道 2,所以来看看 TIMx_CCR2 寄存器, 描述如图:

    在输出模式下,捕获/比较寄存器影子寄存器的值与 CNT 的值比较,根据比较结果产生相应动作,利用这点,我们通过修改这个寄存器的值,就可以控制 PWM 的占空比了。

    硬件设计

    实现功能

    使用 TIM3 通道 2(由 PB5 复用)输出 PWM, PB5 引脚连接了 LED0, 从而实现 PWM 输出控制 LED0 亮度(实现呼吸灯的效果)。

    硬件资源

    本实验基于正点原子战舰V4开发板
    1) LED 灯
    LED0 – PB5
    2)定时器 3 输出通道 2(由 PB5 复用)

    程序设计

    涉及的HAL库函数

    1. HAL_TIM_PWM_Init 函数

    定时器的 PWM 输出模式初始化函数,其声明如下:
    HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim);
    该函数用于初始化定时器的 PWM 输出模式。形参 1 是 TIM_HandleTypeDef 结构体类型指针变量,基本定时器的时候已经介绍。
    注意事项:该函数实现的功能以及使用方法和 HAL_TIM_Base_Init 类似,作用都是初始化定时器的ARR 和 PSC 等参数。为什么 HAL 库要提供这个函数而不直接让我们使用 HAL_TIM_Base_Init函数呢?这是因为 HAL 库为定时器的针对 PWM 输出定义了单独的 MSP 回调函数HAL_TIM_PWM_MspInit,所以当我们调用 HAL_TIM_PWM_Init 进行 PWM 初始化之后,该函数内部会调用 MSP 回调函数 HAL_TIM_PWM_MspInit。当我们使用 HAL_TIM_Base_Init 初始化定时器参数的时候,它内部调用的回调函数是HAL_TIM_Base_MspInit。

    2. HAL_TIM_PWM_ConfigChannel 函数

    定时器的 PWM 通道设置初始化函数。其声明如下:
    HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim,
    TIM_OC_InitTypeDef *sConfig, uint32_t Channel);
    该函数用于设置定时器的 PWM 通道。形参 1 是 TIM_HandleTypeDef 结构体类型指针变量,用于配置定时器基本参数。
    形参 2 是 TIM_OC_InitTypeDef 结构体类型指针变量,用于配置定时器的输出比较参数。
    重点了解一下 TIM_OC_InitTypeDef 结构体指针类型,其定义如下:

    typedef struct
    {
    uint32_t OCMode; /* 输出比较模式选择,寄存器的时候说过了,共 8 种模式 */
    uint32_t Pulse; /* 设置比较值 */
    uint32_t OCPolarity; /* 设置输出比较极性 */
    uint32_t OCNPolarity; /* 设置互补输出比较极性 */
    uint32_t OCFastMode; /* 使能或失能输出比较快速模式 */
    uint32_t OCIdleState; /* 选择空闲状态下的非工作状态(OC1 输出) */
    uint32_t OCNIdleState; /* 设置空闲状态下的非工作状态(OC1N 输出) */
    } TIM_OC_InitTypeDef;
    

    我们重点关注前三个结构体成员。成员变量 OCMode 用来设置模式,这里我们设置为 PWM模式 1。成员变量 Pulse 用来设置捕获比较值。成员变量 TIM_OCPolarity 用来设置输出极性。
    形参 3 是定时器通道,范围: TIM_CHANNEL_1 到 TIM_CHANNEL_4。这里我们使用的是定时器 3 的通道 2,

    3. HAL_TIM_PWM_Start 函数

    定时器的 PWM 输出启动函数,其声明如下:
    HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
    用于使能通道输出和启动计数器,即启动 PWM 输出。
    形参都在前面有介绍过,不多说。
    注意事项:对于单独使能定时器的方法,在基本定时器有提到。实际上, HAL 库也同样提供了单独使能定时器的输出通道函数,函数为:
    void TIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel,
    uint32_t ChannelState);
    HAL_TIM_PWM_Start 函数内部也调用了该函数。

    4. HAL_TIM_ConfigClockSource 函数

    配置定时器时钟源函数,其声明如下:
    HAL_StatusTypeDef HAL_TIM_ConfigClockSource(TIM_HandleTypeDef *htim,
    TIM_ClockConfigTypeDef *sClockSourceConfig);
    形参 1 是 TIM_HandleTypeDef 结构体类型指针变量。
    形参 2 是 TIM_ClockConfigTypeDef 结构体类型指针变量,用于配置定时器时钟源参数。
    TIM_ClockConfigTypeDef 定义如下:

    typedef struct
    {
    uint32_t ClockSource; /* 时钟源 */
    uint32_t ClockPolarity; /* 时钟极性 */
    uint32_t ClockPrescaler; /* 定时器预分频器 */
    uint32_t ClockFilter; /* 时钟过滤器 */
    } TIM_ClockConfigTypeDef;
    

    注意事项:
    该函数主要配置 TIMx_SMCR 寄存器。默认情况下,定时器的时钟源是内部时钟。本实验就是使用内部时钟的,所以我们不用对时钟源就行初始化,默认即可。这里只是让大家知道有这个函数可以设定时器的时钟源。比如用 HAL_TIM_ConfigClockSource 初始化选择内部时钟,
    方法如下:

    TIM_HandleTypeDef timx_handle; /* 定时器 x 句柄 */
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; /* 选择内部时钟 */
    HAL_TIM_ConfigClockSource(&timx_handle, &sClockSourceConfig);
    

    配置步骤



    代码实现

  • 代码基于战舰开发板中的跑马灯实验上实现的,所以只进行定时器3和定时器3通道二的编程
    1. 新建tim.c与tim.h文件
    2. 编写定时器3通道2初始化函数
    /* 通用定时器3 PWM输出通道2句柄 */
    TIM_HandleTypeDef g_tim3_pwn_ch2_handle;
    
    /* 通用定时器初始化函数 */
    void TIM_3_PWM_CH2_init(uint16_t psc,uint16_t arr)
    {
        TIM_OC_InitTypeDef tim3_oc_handle;                              //定时器的输出比较(OC)模式句柄。
    
        g_tim3_pwn_ch2_handle.Instance = TIM3;                         //定时器3
        g_tim3_pwn_ch2_handle.Init.Prescaler = psc;                     //预分频值
        g_tim3_pwn_ch2_handle.Init.Period = arr;                        //自动重装载值——设置PWM周期
        g_tim3_pwn_ch2_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
        HAL_TIM_PWM_Init(&g_tim3_pwn_ch2_handle);
    
        tim3_oc_handle.OCMode = TIM_OCMODE_PWM1;                       //输出比较模式为PWM1
        tim3_oc_handle.Pulse = arr/2;                                //设置占空比为50%
        tim3_oc_handle.OCPolarity = TIM_OCPOLARITY_LOW;               //输出比较极性为低
    
        HAL_TIM_PWM_ConfigChannel(&g_tim3_pwn_ch2_handle, &tim3_oc_handle, TIM_CHANNEL_2);     //PWM输出通道2初始化
        HAL_TIM_PWM_Start(&g_tim3_pwn_ch2_handle, TIM_CHANNEL_2);                            //启动定时器3 PWM输出通道2
    
    }
    
    /* 定时器输出PWM MSP初始化函数 */
    void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
    {
        if(htim->Instance==TIM3)
        {
            GPIO_InitTypeDef gpio_init_struct;
            __HAL_RCC_GPIOB_CLK_ENABLE();                               /* 使能通道2时钟 */
            __HAL_RCC_TIM3_CLK_ENABLE();                                 /* TIM3时钟使能 */
    
            gpio_init_struct.Pin = GPIO_PIN_5;                   /* CH2引脚 */
            gpio_init_struct.Mode = GPIO_MODE_AF_PP;            /* 复用推挽输出 */
            gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
            gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
            HAL_GPIO_Init(GPIOB, &gpio_init_struct);       /* 初始化CH2 */   
            
            __HAL_RCC_AFIO_CLK_ENABLE();                                /* 使能复用功能时钟 */
            __HAL_AFIO_REMAP_TIM3_PARTIAL();                           /* 重映射TIM3部分引脚 */
        
        }
    }
    
    
    1. 在main.c文件实现定时器初始化以及呼吸灯的具体效果
    /* 通用定时器3 PWM输出通道2句柄 */
    extern TIM_HandleTypeDef g_tim3_pwn_ch2_handle;
    int main(void)
    {
        uint16_t ledrpwmval = 0;
        uint8_t dir = 1;
        HAL_Init();                                 /* 初始化HAL库 */
        sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟,72M */
        delay_init(72);                             /* 初始化延时函数 */
        led_init();                                 /* 初始化LED */
        TIM_3_PWM_CH2_init(500-1,72-1);
    
    
        while(1)
        {
            delay_ms(10);
    
            if (dir)ledrpwmval++;               /* dir==1 ledrpwmval递增 */
            else ledrpwmval--;                  /* dir==0 ledrpwmval递减 */
            if (ledrpwmval > 300)dir = 0;       /* ledrpwmval到达300后,方向为递减 */
            if (ledrpwmval == 0)dir = 1;        /* ledrpwmval递减到0后,方向改为递增 */
    
            /* 修改比较值控制占空比 */
            __HAL_TIM_SET_COMPARE(&g_tim3_pwn_ch2_handle, TIM_CHANNEL_2, ledrpwmval);
    
        }
    }
    

    在代码中,通过if判断不断改变ledrpwmval的值,然后使用 __HAL_TIM_SET_COMPARE 修改定时器3的通道2的比较值,从而控制PWM信号的占空比,实现呼吸灯效果。

    作者:玛卡巴卡也会有悲伤

    物联沃分享整理
    物联沃-IOTWORD物联网 » 正点原子STM32HAL库学习实践:stm32f103zet6通用定时器PWM输出实现呼吸灯效果实验

    发表回复