其他分享
首页 > 其他分享> > [学习笔记]STM32F1通用定时器-PWM(寄存器、标准库、HAL库)

[学习笔记]STM32F1通用定时器-PWM(寄存器、标准库、HAL库)

作者:互联网

目录

11. 通用定时器-PWM

1. 实验内容及步骤:

2. 硬件说明

3. 步骤详细讲解

3.1定时器配置

3.2定时器输出PWM配置

4. 程序设计(寄存器)

5. 程序设计(标准库)

6. 程序设计(HAL库)

7. 实验结果

8. 源码下载


 

11. 通用定时器-PWM

1. 实验内容及步骤:

       1. 通过定时器2,经过分频和重装载值生成50Hz=20ms的周期的中断,并且在50次中断后(即1s),让LED反转。

       2. 通过定时器2通道1(PA0)的PWM输出,实现LED呼吸灯。

2. 硬件说明

本电路的硬件如下图所示,LED灯与GPIOB5相连,低电平亮,高电平灭。

 

3. 步骤详细讲解

3.1定时器配置

       1. 时钟的选择,选择内部时钟源;

       2. 计数模式的选择,选择向上计数;

       3. 定时器分频选择,内部时钟源=2*APB1=72M,分频720得到定时器的时钟100KHz

       4. 自动重装载:配置为2000,即100KHz/2000=50Hz=20ms(周期);

       5. 中断源配置:配置只有上下溢出中断,使能中断。

      1. 时钟选择

       内部时钟源:

       TIMx_SMCR寄存器中的SMS[2:0]=000, 关闭从模式

       本实验中使用内部时钟:

              TIMx_SMCR &= ~((u32)0x07<<0);    //关闭从模式

       TIMx_CR1寄存器的CEN(第0位),决定定时器的使能/禁止。

       内部时钟源(CK_INT)    CEN位被写成’1’。

       本实验中:

              TIMx_CR1 |= ((u32)0x01<<0);    //使能计数器

      

      2. 计数方向选择

       TIMx_CR1的CMS[1:0]=00, 边沿对齐模式。计数器依据方向位(DIR)向上或向下计数。

       TIMx_CR1的DIR位,决定了定时器的计数方向。

       本实验中配置位向上计数模式:

              TIMx_CR1 &= ~((u32)0x03<<5);     // 边沿对齐模式

              TIMx_CR1 &= ~((u32)0x01<<4);        //向上计数模式

      3. 定时器时钟分频

       定时器时钟为CK_CNT=F(CK_PSC)/(PSC[15:0]+1)。

       本实验中:

              已知定时器2的时钟源F(CK_PSC)=2*APB1(72MHz)

              分频值720-1,定时器时钟CK_CNT=72M/(720-1+1)=100KHz

      4. 自动重装在值

       本实验中:

              自动重装载值为2000,则定时器中断周期为:100KHz/2000=50Hz=20ms。

      5. 更新中断使能

       配置只有上下溢出中断,由TIMx_CR1寄存器中的USR(第二位)决定。

       TIMx_CR1 |= ((u32)0x01<<2);        //只有上下溢出才中断

       更新中断使能在TIMx_DIER寄存器中的UIE(第0位)。

       本实验中:

              TIMx_DIER |= (u32)0x01<<0;     //使能中断

       NVIC中断优先级配置和使能;

       SET_NVIC_IP(TIM2_IRQn,14);          //NVIC中断使能(这个是自己写的函数)

       此时配置定时器2中断优先级为14,使能NVIC TIM2中断管理器。

      6. 中断函数

       在中断函数中,通过判断TIMx_SR状态寄存器中的UIF(第0位)来确定是否为更新中断。

       为1,则表示为更新中断,需要软件清除。

       中断函数如下所示:

              进入中断50次后(即1S),LED灯反转。

3.2定时器输出PWM配置

      PWM配置步骤:

       1. PWM的GPIO配置;

       2. TIM2配置;

       3. PWM占空比设置;

       4. 配置有效电平极性;

       5. 配置PWM的模式;

       6. 配置相应的预装载寄存器缓冲;

       7. PWM输出使能;

      1. PWM的GPIO配置

       TIM2的比较通道1(PA0)应该配置为推挽复用输出。

       本函数自己写的:

       GPIOX_PIN_SetMode(GPIOA,0,GPIOx_AF_PP|GPIOx_OUT_10M);   //PA0 复用推挽输出

      2. TIM2配置

       如3.1 定时器配置一致。

       本实验中配置TIM2的时钟为:72M/720=100KHz(定时器时钟)100K/2000=50Hz=20ms(周期)

      3.PWM占空比设置

       PWM占空比由TIMx_CCRx寄存器进行配置,寄存器的说明如下所示:

       本实验中配置初始占空比为50%,即:

              TIMx_CCRx= 1000;          //占空比50%

      4. 配置有效电平极性

       OCx的极性即比较后有效结果时,输出的电平。其寄存器说明如下所示:

       TIMx_CCER寄存器中的CC1P位(位1),决定有效电平是高电平还是低电平。

       本实验中配置为高电平有效:

       TIMx_CCER &= ~(0x01<<1);   //OC1高电平有效

      5. 配置PWM的模式

       在TIMx_CCMRx寄存器中的OCxM位写入’110’(PWM模式1)或’111’(PWM模式2)。TIMx_CCMRx寄存器中的OCxM位如下所示:

       在TIMx_CCMRx寄存器中的OCxM位写入’110’(PWM模式1)或’111’(PWM模式2)

              向上计数配置:

              PWM模式1:当TIMx_CNT<TIMx_CCRx时PWM信号参考OCxREF为高;

              向下计数的配置:

              当TIMx_CNT>TIMx_CCRx时参考信号OCxREF为低;

       本实验中使用PWM模式,使用PWM模式1:

       TIMx_CCMRx &= ~(0x07<<4);  //清空输出比较1模式

TIMx_CCMRx |= 0x06<<4;     //模式1

      6. 配置相应的预装载寄存器

这一步骤是配置其作用主要为:写比较寄存器后,是否需要缓冲。有缓冲的话,更新事件到来才更新到相应的寄存器;没有的话就直接更新到相应的寄存器。有没有问题都不是很大。

       本实验中开启预装载使能:

       TIMx_CCMR1 |= 0x01<<3;     //输出比较1预装载使能

      7.PWM输出使能

       TIMx_CCER寄存器中的CCxE位控制OCx输出使能。OCx位如下图所示:

       使用PWM需要使能输出:

       TIMx_CCER |= (0x01<<0);   //开启输出

      8.呼吸灯实现

       没10ms进行PWM的值增加10或减小10,实现呼吸灯的效果。

4. 程序设计(寄存器)

       定时器中断见源码,这里通过TIM2-PWM实现LED成呼吸灯

       配置步骤:

       1. GPIO时钟使能,TIM时钟使能;

       2. 配置PWM输出的GPIO为:推挽输出;

       3. 配置定时器TIM2的分频,预装载值,上下计数模式,定时器使能;

       4. 中断管理器NVIC使能,优先级配置(如果需要中断的话);

       5. 配置PWM占空比,模式,有效极性,比较使能;

       6. 编写PWM修改函数。

       源码:这里只列出配置TIM2-PWM部分

//初始化定时器2
//APB1 = 72MHz
void TIM2_Config(u16 psc,u16 arr)
{
    //时钟
    RCC->APB1ENR    |=  RCC_APB1ENR_TIM2EN;
    
    //时钟选择(内部时钟源)
	TIM2->SMCR &= ~((u32)0x07<<0);	    //关闭从模式
    //计数方向选择
    TIM2->CR1 &= ~((u32)0x03<<5);	    // 边沿对齐模式
	TIM2->CR1 &= ~((u32)0x01<<4);		//向上计数模式		
    //预分频
    TIM2->PSC = psc;                    //分频
    //重装载值
    TIM2->ARR = arr;                    //重装在值
    //清空计数器
    TIM2->CNT = 0;                      //清空当前计数器
    //使能中断
    TIM2->CR1 |= ((u32)0x01<<2);	    //只有上下溢出才中断
    TIM2->DIER |= (u32)0x01<<0;         //允许更新中断
    SET_NVIC_IP(TIM2_IRQn,14);          //NVIC中断使能
    //使能定时器
    TIM2->CR1 |= ((u32)0x01<<0);	    //使能计数器
    
}

/*
    TIM2_PWM_配置
*/
void TIM2_PWM_Config(void)
{
    //GPIO初始化
    GPIOX_PIN_SetMode(GPIOA,0,GPIOx_AF_PP|GPIOx_OUT_10M);   //PA0 复用推挽输出 
    //TIM2初始化
    TIM2_Config(720-1,2000-1);  // 72M/720=100KHz    100K/2000=50Hz=20ms(周期)
    //比较寄存器
    TIM2->CCR1 = 1000;          //占空比50%
    //输出比较1模式(模式1)
    TIM2->CCMR1 &= ~(0x07<<4);  //清空输出比较1模式
    TIM2->CCMR1 |= 0x06<<4;     //模式1
    //输出比较1预装载使能
    TIM2->CCMR1 |= 0x01<<3;     //输出比较1预装载使能
    //比较1输出极性
    TIM2->CCER &= ~(0x01<<1);   //OC1高电平有效
    //使能比较1输出
    TIM2->CCER |= (0x01<<0);   //开启
    
}

/*
    设置比较输出1的PWM
*/
void Set_TIM2_PWM_CH1(u16 pwm)
{
    TIM2->CCR1  =   pwm;
}


void TIM2_IRQHandler(void)
{
    static u16 i=0;
    if(TIM2->SR&0x01)   //判断更新中断
    {
      i++;
        if(i>=50-1) //50次  1S
        {
            i=0;
            //BPB_OUT(5) = !BPB_OUT(5);
            printf("1S计时\r\n");
        }
    }
    TIM2->SR &= ~((u32)0x01<<0);    //清除中断标志位
}

5. 程序设计(标准库)

       配置步骤:

       1. GPIO时钟使能,TIM时钟使能;

       2. 配置PWM输出的GPIO为:推挽输出。通过GPIO_InitTypeDef结构体配置;

       3. 配置定时器TIM2的分频,预装载值,上下计数模式。通过TIM_TimeBaseInitTypeDef结构体配置;

       4. 中断管理器NVIC使能,优先级配置(如果需要中断的话)。通过NVIC_InitTypeDef结构体进行配置;

       5. 配置PWM占空比,模式,有效极性,比较使能。通过TIM_OCInitTypeDef结构体配置;

       6. 定时器使能函数,使用:TIM_Cmd函数使能;

       7. PWM修改函数为:TIM_SetCompare1函数。

       源码:这里只列出配置TIM2-PWM部分

#define TIM_x               TIM2
#define TIM_NVIC_IRQ       TIM2_IRQn
#define TIM_NVIC_PP        13
#define TIM_NVIC_SP        0

#define PWM_GPIOX           GPIOA
#define PWM_GPIOX_PIN       GPIO_Pin_0    
#define PWM_GPIOX_MODE      GPIO_Mode_AF_PP     //复用推挽
//初始化定时器2
//APB1 = 72MHz
void TIM2_Config(u16 psc,u16 arr)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    NVIC_InitTypeDef  NVIC_InitStruct;
    //时钟
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM2EN,ENABLE);    //TIM2定时器
    //定时器基本配置   
    TIM_TimeBaseInitStruct.TIM_Prescaler    =   psc;      //分频  
    TIM_TimeBaseInitStruct.TIM_CounterMode  =   TIM_CounterMode_Up; //向上计数
    TIM_TimeBaseInitStruct.TIM_Period    =   arr;       //从装在值
    TIM_TimeBaseInit(TIM_x,&TIM_TimeBaseInitStruct);
    //中断配置
    TIM_ITConfig(TIM_x,TIM_IT_Update,ENABLE);
    //优先级配置
    NVIC_InitStruct.NVIC_IRQChannel     =   TIM_NVIC_IRQ;
    NVIC_InitStruct.NVIC_IRQChannelCmd  =   ENABLE;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority   =   TIM_NVIC_PP;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority  =   TIM_NVIC_SP;
    NVIC_Init(&NVIC_InitStruct);    //设置中断优先级    
    //定时器使能
    TIM_Cmd(TIM_x,ENABLE);    
}

/*
    TIM2_PWM_配置
*/
void TIM2_PWM_Config(void)
{
    GPIO_InitTypeDef  GPIO_InitStruct;
    TIM_OCInitTypeDef  TIM_OCInitStruct;
    //GPIO初始化
    GPIO_InitStruct.GPIO_Mode   =   PWM_GPIOX_MODE;
    GPIO_InitStruct.GPIO_Pin    =   PWM_GPIOX_PIN;
    GPIO_InitStruct.GPIO_Speed  =   GPIO_Speed_10MHz;
    GPIO_Init(PWM_GPIOX, &GPIO_InitStruct);     //复用推挽输出 
    //TIM2初始化
    TIM2_Config(720-1,2000-1);  // 72M/720=100KHz    100K/2000=50Hz=20ms(周期)
    //通道配置
    TIM_OCInitStruct.TIM_OCMode =   TIM_OCMode_PWM1;    //PWM1模式
    TIM_OCInitStruct.TIM_OutputState    =   TIM_OutputState_Enable; //比较使出使能
    TIM_OCInitStruct.TIM_OCPolarity     =   TIM_OCPolarity_High;    //有效电平 高电平
    TIM_OCInitStruct.TIM_Pulse      =   1000;           //占空比50%
    TIM_OC1Init(TIM_x, &TIM_OCInitStruct);
    
}


//中断函数
void TIM2_IRQHandler(void)
{
    static u16 i=0;
    if(TIM_GetITStatus(TIM_x,TIM_IT_Update))   //判断更新中断
    {
      i++;
        if(i>=50-1) //50次  1S
        {
            i=0;
            //BPB_OUT(5) = !BPB_OUT(5);
            printf("1S计时\r\n");
        }
    }
    TIM_ClearITPendingBit(TIM_x,TIM_IT_Update);    //清除中断标志位
}

6. 程序设计(HAL库)

       HAL库与标准库的定时器区别主要在:HAL库进行高度封装,使用相对简便,很多判断标志位和清除标志位都由HAL库完成了。

       总结出HAL库的操作:初始化+使能。

       配置步骤:

       1. GPIO时钟使能,TIM时钟使能;

       2. 通过HAL_TIM_Base_Init函数初始化定时器操作;

       3. 通过HAL_TIM_Base_Start_IT函数使能定时器,并使能中断;

       4. 通过HAL_NVIC_SetPriority、HAL_NVIC_EnableIRQ使能NVIC中断管理和优先级;

       5. 通过HAL_TIM_OC_ConfigChannel初始化PWM配置;

       6. 通过HAL_TIM_OC_Start使能PWM;

       7. 可在HAL_TIM_Base_MspInit回调函数中初始化GPIO;

       8. 在中断中调用HAL_TIM_IRQHandler中断处理函数(其具有标志位判断和清除功能,能够降低开发难度);

       9. 在更新回调函数(HAL_TIM_PeriodElapsedCallback)中编写更新中断内容;

       源码:这里只列出配置TIM2-PWM部分

#include "My_GP_TIM.h"
#include "stdio.h"
#include "My_exti.h"
#include "My_bit.h"
#include "My_gpio.h"
//TIM2
TIM_HandleTypeDef TIM2_HandleStruct;

#define TIM_x              TIM2
#define TIM_NVIC_IRQ       TIM2_IRQn
#define TIM_NVIC_PP        13
#define TIM_NVIC_SP        0

#define PWM_GPIOX           GPIOA
#define PWM_GPIOX_PIN       GPIO_PIN_0    
#define PWM_GPIOX_MODE      GPIO_MODE_AF_PP     //复用推挽
//初始化定时器2
//APB1 = 72MHz
void TIM2_Config(u16 psc,u16 arr)
{
    //时钟
    __HAL_RCC_TIM2_CLK_ENABLE();    //TIM2定时器
    //定时器基本配置  
    TIM2_HandleStruct.Instance   =   TIM_x;
    TIM2_HandleStruct.Init.Prescaler =   psc;    //分频
    TIM2_HandleStruct.Init.CounterMode   =   TIM_COUNTERMODE_UP; //向上计数
    TIM2_HandleStruct.Init.Period    =   arr;    //从装在值
    TIM2_HandleStruct.Channel    =   HAL_TIM_ACTIVE_CHANNEL_1;   //通道1
    HAL_TIM_Base_Init(&TIM2_HandleStruct);
    //中断配置
    HAL_TIM_Base_Start_IT(&TIM2_HandleStruct);
    //优先级配置
    HAL_NVIC_SetPriority(TIM_NVIC_IRQ,13,0);    //响应优先级为13 最低   
    HAL_NVIC_EnableIRQ(TIM_NVIC_IRQ);           //使能中断线    
 
}

/*
    TIM2_PWM_配置
*/
void TIM2_PWM_Config(void)
{
    GPIO_InitTypeDef GPIO_Init;
    TIM_OC_InitTypeDef TIM_OC_InitStruct;
    //时钟
    __HAL_RCC_GPIOB_CLK_ENABLE();
    //GPIO初始化
    GPIO_Init.Mode  =   PWM_GPIOX_MODE;
    GPIO_Init.Pin   =   PWM_GPIOX_PIN;
    GPIO_Init.Speed =   GPIO_SPEED_FREQ_MEDIUM;
    HAL_GPIO_Init(PWM_GPIOX,&GPIO_Init);   //复用推挽输出 
    //TIM2初始化
    TIM2_Config(720-1,2000-1);  // 72M/720=100KHz    100K/2000=50Hz=20ms(周期)
    //通道配置
    TIM_OC_InitStruct.OCMode    =   TIM_OCMODE_PWM1;        //PWM1模式
    TIM_OC_InitStruct.OCPolarity    =   TIM_OCPOLARITY_HIGH;    //有效电平 高电平
    TIM_OC_InitStruct.OCIdleState   =   TIM_OCIDLESTATE_SET;    //比较使出使能
    TIM_OC_InitStruct.Pulse =   1000;   //占空比50%
    HAL_TIM_OC_ConfigChannel(&TIM2_HandleStruct,&TIM_OC_InitStruct,TIM_CHANNEL_1);
    HAL_TIM_OC_Start(&TIM2_HandleStruct,TIM_CHANNEL_1);
}


//中断函数
void TIM2_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&TIM2_HandleStruct);
}

//更新事件回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    static u16 i=0;
    i++;
    if(i>=50-1) //50次  1S
    {
        i=0;
        //BPB_OUT(5) = !BPB_OUT(5);
        printf("1S计时\r\n");
    }    
}

7. 实验结果

       1. 每隔1S,定时器中断中会输出“1S计时”;

       2. PWM呼吸灯实验,LED灯会呈现呼吸效果;

8. 源码下载

        源码下载

标签:TIM2,定时器,HAL,NVIC,TIM,GPIO,PWM,STM32F1
来源: https://blog.csdn.net/m0_48376567/article/details/118719032