【STM32F103】TIM定时器&PWM

发布时间:2024年01月03日

定时器分类

STM32F1中除了互联型产品(STM32F103C8T6为64KB Flash 中容量产品),其余有8个定时器。

可以8个定时器分为高级,通用,基本三种。

高级定时器有两个,分别是TIM1和TIM8。

通用定时器有四个,是TIMx(x=2~4)

基本定时器有两个,是TIMx(x=6~7)

功能上高级>通用>基本

不过高级定时器一般也用不着,如果只是普通的计时的话基本定时器也就够用了,但是基本定时器没有输出比较,没法实现PWM(脉冲宽度调制),因此我们主要使用的是通用定时器,并且STM32F103中通用定时器的资源也是最多的。

下图截自《ARM Cortex-M3嵌入式原理及应用(基于STM32F103微控制器)》的第150页。

通用定时器

?下图截自《STM32F10xxx参考手册(中文)》第253页

不太严谨地介绍一下定时器的工作流程。时钟源每传来n(n=预分频器的值)次脉冲,都会使得计数器工作一次。如果选择的是向上计数模式,那么计数器每工作一次都会使得计数器的值+1,直到值等于了自动重装载寄存器的值,那么触发一次中断,并且计数器清零。

如果选择的是向下计数模式,那么计数器每工作一次都会使得计数器的值-1,直到值等于0,那么触发一次中断,并且计数器的值变为自动重装载寄存器的值。

如果选择向上向下计数模式,那么计数器工作一次会使得计数器的值+1或者-1,计数器的值不会被重置,会一直在0和自动重装载寄存器的值之间上下跳动。

一般情况下这三种工作模式对于计数的效果都是一样的,但是当我们需要利用到计数器里的值的时候就有差别了(例如PWM),通常我们使用的是向上计数模式,这也比较符合我们的直觉。

?上述的介绍中提到的是以下三个寄存器。

可以看出它们都是只有16位,也就是说可以设置的最大数值为2^16-1(65535)。

并且从工作流程中也可以得知,定时器溢出的频率公式为

时钟源频率/(自动重装载寄存器的值+1)/(预分频器的值+1)=CK_INT/(ARR+1)/(PSC+1)

PWM脉冲宽度调制

STM32F103中每个通用定时器都可以输出4路PWM,而每个高级定时器都可以输出7路PWM,所以理论上,STM32最多是可以同时产生30路PWM。

PWM简单来说就是一段有高电平也有低电平的脉冲信号。

我们一般点亮一个LED灯,就是把LED灯的短脚接地,然后通过GPIO口从LED灯的长脚传入高电平,我们假设此刻LED的亮度为100。如果我们改用PWM来代替原本的高电平,并且PWM传入的脉冲信号中高低电平各占50%,那么此刻LED的亮度就为50%了,因为人眼大概只能接受每秒25帧左右的图像,因此只要PWM够快,那么我们就看不出LED实际上是亮灭亮灭的,由于人眼的残留影像,我们看到的只是LED灯变得暗了些。

之所以把PWM和定时器放在一起,是因为定时器的硬件中就含有捕获比较寄存器。

我们可以给这个寄存器设置一个阈值,当定时器的计数器的值小于这个阈值的时候我们就输出高电平,反之输出低电平(具体看选择的PWM输出模式)。

综上我们可以得知,PWM输出的频率等于定时器的溢出中断频率。

固件库函数

定时器相关的函数还是非常多的,接下来就以计数1ms为目的来说明相关函数。

首先需要打开相关定时器的外设时钟,不过不属于定时器库函数里的,就不介绍。

下一步选择时钟源,默认是使用内部时钟源(72MHz),所以也可以不指定。

然后初始化时基单元(配置自动重装载寄存器,预分频器,时钟分频,计数模式)

接着是中断使能以及配置NVCI相关的中断优先级

最后是使能计数器就可以开始计数了。

TIM_InternalClockConfig

上面三个函数是常用的用来指定时钟源的函数。

第一个也是默认的,使用的是内部时钟作为时钟源。

第二个可以将其他定时器的溢出信号作为时钟源,也就是两个定时器联动,这样可以将最大的计时数平方一次,也就是可以记更久的时间,不过我们也是可以使用软件来完成这种效果的。

第三个可以将时钟源接到外部的时钟。

TIM_TimeBaseInit

初始化时基单元,参数一指定定时器资源,参数二传入一个TIM_TimeBaseInitTyepDef类型的结构体变量用于初始化配置。

TIM_Prescale:预分频器的值,0~65535。

TIM_CounteMode:计数器模式,一般使用向上计数TIM_CounterMode_Up

TIM_Period:自动重装载计数器的值,0~65535。

TIM_ClockDivision:时钟分频,TIM_CKD_DIV1

TIM_RepetitionCounter:重复计数器的值,只有高级定时器才用的到,基本定时器和通用定时器随便给个0就行。

TIM_ITConfig

中断使能,参数一选择定时器资源,参数二选择中断源,参数三给个ENABLE。

中断源一般就指定第一个TIM_IT_Update

这样每计数一轮都会触发一次中断。

TIM_Cmd

使能计数器。

使能之后计数器就开始运作了,每次记满一轮之后就会进入一次中断,我们可以从“startup_stm32f10x_md.s”中找到对应的中断函数。

由于可进入这个中断函数的中断源有多个(参考上面中断使能的参数表),因为我们需要查询中断标志位是否是我们所规定的TIM_IT_Update触发的,是的话我们还需要手动把标志位清除(其他中断源也是一样)。

TIM_GetITStatus

查询中断标志位,参数二给的一样的TIM_IT_Update

TIM_ClearITPendingBit

清除中断标志位,参数二给的一样的TIM_IT_Update

上面的函数就是用来计数用的,接下来就是如何输出PWM的了。

把上面计数的流程走一遍,然后把中断相关的都删掉,再加上配置输出比较单元以及初始化GPIO口的函数即可。

TIM_OC1Init

配置通道1的输出比较单元。这里需要注意的是不同定时器资源的不同通道对应的GPIO输出口是不一样的,TIM2的通道1对应的是GPIOA的0号引脚,具体可以查询引脚定义表。并且需要配置为复用推挽输出模式GPIO_Mode_AF_PP

参数一指定定时器资源,参数二传入TIM_OCInitTypeDef类型的参数。

一共有八个成员变量,但是我们只需要用到我圈起来的4个,其余为高级定时器使用的。

TIM_OCMode:设置输出比较的模式,一般我们选择TIM_OCMode_PWM1,在这个模式下,当计数器的值小于阈值时输出设置的极性(下面会设置),反之输出相反的极性。

TIM_OCPolarity:输出比较的极性,这个根据需求来设置,设置为高电平的参数为TIM_OCPolarity_High

TIM_OutputState:设置输出使能,TIM_OutputState_Enable

TIM_Pulse:设置阈值,0~65535。因此我们定时器的自动重装载寄存器的值最好不要超过这个阈值,并且由于容易计算PWM的占空比,一般我会把自动重装载寄存器的值设为10的倍数。

以上就足够我们输出PWM了,如果我们需要实现呼吸灯的效果,那么还需要动态地调整PWM的阈值。

TIM_SetCompare1

设置通道1的输出比较阈值。

参数一指定定时器资源,参数二重新设置阈值大小。?

通用定时器计数1ms代码

#include "stm32f10x.h"                  // Device header
#include "OLED.h"

uint32_t count=0;

int main(void){
    OLED_Init();
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);     //打开TIM2的外设时钟
    TIM_InternalClockConfig(TIM2);                          //选择内部时钟(72MHz)作为时钟源
    TIM_TimeBaseInitTypeDef itd;
    itd.TIM_ClockDivision=TIM_CKD_DIV1;                     //时钟1分频
    itd.TIM_CounterMode=TIM_CounterMode_Up;                 //向上计数模式
    itd.TIM_Period=1000-1;                                  //设置自动重装器的值
    itd.TIM_Prescaler=72-1;                                 //设置预分频器的值
    //TIM2的计数器溢出频率(每秒中断次数)为
    //(72MHz/1(1分频)/(1000-1+1)(自动重装器的值+1)/(72-1+1)(预分频器的值+1))=1000(ms级,即1ms溢出一次)
    itd.TIM_RepetitionCounter=0;                            //重复计数器的值,但是仅高级定时器有效
    TIM_TimeBaseInit(TIM2,&itd);
    TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);                //开启定时器中断,TIM为中断源
    
    //配置中断优先级
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);         //抢占优先级和响应优先级各占两位
    NVIC_InitTypeDef itd1;
    itd1.NVIC_IRQChannel=TIM2_IRQn;                         //指定TIM2的中断通道
    itd1.NVIC_IRQChannelCmd=ENABLE;
    itd1.NVIC_IRQChannelPreemptionPriority=2;               //抢占优先级为2
    itd1.NVIC_IRQChannelSubPriority=2;                      //响应优先级为2
    NVIC_Init(&itd1);
    
    TIM_Cmd(TIM2,ENABLE);                                   //使能定时器
    
    while(1){
        OLED_ShowNum(2,1,count,6);
    }
}

void TIM2_IRQHandler(void){
    if(TIM_GetITStatus(TIM2,TIM_FLAG_Update)){          //判断是否是TIM中断源引发的中断
        ++count;    
        TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);    //清除标志位
    }
}

效果

?通用定时器使用PWM实现LED呼吸灯

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

int main(void){
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);     //打开TIM2的外设时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    GPIO_InitTypeDef gitd;
    gitd.GPIO_Mode=GPIO_Mode_AF_PP;                         //配置为复用推挽输出
    gitd.GPIO_Pin=GPIO_Pin_0;
    gitd.GPIO_Speed=GPIO_Speed_2MHz;
    GPIO_Init(GPIOA,&gitd);
    
    TIM_InternalClockConfig(TIM2);                          //选择内部时钟(72MHz)作为时钟源
    TIM_TimeBaseInitTypeDef itd;
    itd.TIM_ClockDivision=TIM_CKD_DIV1;                     //时钟1分频
    itd.TIM_CounterMode=TIM_CounterMode_Up;                 //向上计数模式
    itd.TIM_Period=100-1;                                   //设置自动重装器的值
    itd.TIM_Prescaler=72-1;                                 //设置预分频器的值
    itd.TIM_RepetitionCounter=0;                            //重复计数器的值,但是仅高级定时器有效
    TIM_TimeBaseInit(TIM2,&itd);
    
    TIM_OCInitTypeDef itd1;
    itd1.TIM_OCMode = TIM_OCMode_PWM1;                      //比较输出模式为PWM1
    itd1.TIM_OCPolarity = TIM_OCPolarity_High;              //输出极性为高电平
    itd1.TIM_OutputState=TIM_OutputState_Enable;            //使能
    itd1.TIM_Pulse=0;                                       //初始化输出比较的阈值          
    TIM_OC1Init(TIM2,&itd1);
    
    TIM_Cmd(TIM2,ENABLE);                                   //使能定时器
    while(1){
        for(int i=0;i<=100;++i){ 
            TIM_SetCompare1(TIM2,i);
            Delay_ms(10);
        }
        for(int i=100;i>=0;--i){
            TIM_SetCompare1(TIM2,i);
            Delay_ms(10);
        }
    }
}

效果

参考

《STM32F10xxx参考手册(中文)》

《STM32F103xx固件函数库用户手册》

b站江科大自化协

《STM32库开发实战指南(基于STM32F103)》

《ARM Cortex-M3嵌入式原理及应用(基于STM32F103微控制器)》

文章来源:https://blog.csdn.net/m0_63235356/article/details/135127080
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。