STM32学习心得十八:通用定时器基本原理及相关实验代码解读

记录一下,方便以后翻阅~
主要内容
1) 三种定时器分类及区别;
2) 通用定时器特点;
3) 通用定时器工作过程;
4) 实验一:定时器中断实验补充知识及部代码解读;
6) 实验二:定时器PWM输出补充知识及部分代码解读;
7)
相关实验
实验一定时器中断实验:通过定时器中断配置,使用定时器3,每隔500ms触发一次中断,后中断服务函数中控制LED实现LED1状态取反;
实验二定时器PWM输出实验:使用定时器3的PWM功能,输出占空比可变的PWM波,用来驱动LED灯,从而达到LED0亮度由暗变亮,又从亮变暗。
实验三:
官方资料:官方资料:《STM32中文参考手册V10》第14章——通用定时器
1. STM32定时器
STM32F103RC和STM32F103ZE总共最多有8个定时器。
在这里插入图片描述
2. 三种定时器区别
在这里插入图片描述
3. 通用定时器功能特点
针对STM3 的通用 TIMx (TIM2、TIM3、TIM4 和 TIM5)定时器的特点:
3.1 可来源于位于低速的APB1总线上(APB1);
3.2 16 位向上、向下、向上/向下(也称中心对齐)计数模式,自动装载计数器(TIMx_CNT);
3.3 16 位可编程(可实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数 为 1~65535 之间的任意数值。
3.4 4 个独立捕获/比较通道(TIMx_CH1~4),这些通道可以用来作为:
3.4.1 输入捕获;
3.4.2 输出比较;
3.4.3 PWM 生成(边缘或中间对齐模式) ;
3.4.4 单脉冲模式输出。
3.5 可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可用 1 个定时器控制另一个定时器)的同步电路。
3.6 如下事件发生时产生中断/DMA(6个独立的IRQ/DMA请求生成器):
3.6.1 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发);
3.6.2 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数) ;
3.6.3 输入捕获;
3.6.4 输出比较;
3.6.5 支持针对定位的增量(正交)编码器和霍尔传感器电路;
3.6.6 触发输入作为外部时钟或者按周期的电流管理。
3.7 STM32 通用定时器可被用于,测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)等。
3.8 使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32 的每个通用定时器都是完全独立的,没有互相共享的任何资源。
4. 通用定时器的计数模式
通用定时器可以向上计数、向下计数、向上向下双向计数模式:
4.1 向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件;
4.2 向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件;
4.3 中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件,然后再从0开始重新计数。
在这里插入图片描述
5. 通用定时器的工作过程
在这里插入图片描述
5.1 上图中:
5.1.1 上半部分可称为时钟发生器,主要目的是产生时钟至PSC预分频器;
5.1.2 PSC预分频器、自动重装载寄存器和CNT计数器构成时基单元;
5.1.3 左下部分称为输入捕获部分,对输入信号进行相关操作;
5.1.4 右下部分称为输出比较部分;
5.1.5 剩余的捕获比较寄存器作为剩余的部分。
5.2 针对5.1.1细讲:
在这里插入图片描述
5.2.1 计数时钟可来源于RCC的TIMxCLK的内部时钟(主要通过APB1经过倍频获得);
5.2.2 计数时钟可来源于外部引脚TIMx_ETR(主要针对TIM2、TIM3、TIM4),经极性选择、边沿检测、预分频器和滤波后产生时钟信号;
5.2.3 计数时钟可来源于ITR0~3(内部触发输入,即来自于其他定时器),经过选择器后作为时钟来源;
5.2.4 计数时钟可来源于TI1F_ED,TI1FP1,TI1FP2(最终来源于TIMx_CH1~4,外部通道)。
5.3 针对5.1.2细讲:
时钟信号先经过PSC预分频器(即除法运算)产生CK_CNT时钟(计数器最终时钟),然后根据触发控制器配置的计数模式(向上,向下或中心对齐)进行计数,计数到重装载值后,会产生溢出事件,可产生触发中断或DMA请求;
5.4针对5.1.3细讲:
捕获通道引脚上的电平(比如上升沿捕获,边沿检测,可计算脉冲宽度);
5.5 针对5.1.4细讲:
可调整输出高低电平的占空比。
6. 实验一定时器中断实验知识补充
6.1 内部时钟选择过程
在这里插入图片描述
AHB时钟经APB1预分频后产生的时钟供通用定时器TIM2,3,4,5用,注意:如果APB1预分频=1,则1倍输出,否则2倍输出
在这里插入图片描述
举例:
再次强调:除非APB1的分频系数是1,否则通用定时器的时钟等于APB1时钟的2倍。
默认调用SystemInit函数情况下:
SYSCLK=72M;
AHB时钟=72M;//AHB预分频1//
APB1时钟=36M;//APB1预分频2//
所以,通用定时器时钟CK_INT应为APB1时钟的2倍,即2*36M=72M,
最终计数时钟CK_CNT时钟由CK_PSC分频后获得。
6.2 计数模式
6.2.1 向下计数(时钟分频因子=1)
在这里插入图片描述
6.2.2 向上计数(时钟分频因子=1)
在这里插入图片描述
6.2.3 中央对齐计数模式(时钟分频因子=1 ARR=6)
在这里插入图片描述
6.3 定时器中断实验相关寄存器
6.3.1 计数器当前值寄存器CNT;
6.3.2 预分频寄存器TIMx_PSC;
6.3.3 自动重装载寄存器(TIMx_ARR);
6.3.4 控制寄存器1(TIMx_CR1);
6.3.5 DMA中断使能寄存器(TIMx_DIER)。
6.4. 定时器中断实验常用库函数
6.4.1 定时器参数初始化:

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

举例:

typedef struct
{
uint16_t TIM_Prescaler;        
uint16_t TIM_CounterMode;     
uint16_t TIM_Period;        
uint16_t TIM_ClockDivision;  
uint8_t  TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef; 

TIM_TimeBaseStructure.TIM_Period = 4999; 
TIM_TimeBaseStructure.TIM_Prescaler =7199; 
TIM_TimeBaseStructure.TIM_ClockDivision =   TIM_CKD_DIV1; 
TIM_TimeBaseStructure.TIM_CounterMode =   TIM_CounterMode_Up; 
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); 

6.4.2 定时器使能函数:

void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

6.4.3 定时器中断使能函数:

void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);

6.4.4 状态标志位获取和清除:

FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);

6.5 定时器中断实现步骤
6.5.1 使能定时器时钟:

RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);

6.5.2 初始化定时器,配置ARR,PSC:

TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

6.5.3 开启定时器中断,配置NVIC:

void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);

6.5.4 使能定时器:

TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

6.5.5 编写中断服务函数:

TIMx_IRQHandler();

6.6 溢出时间计算公式(有计数时钟、重载值和PSC分频系数共同决定):
Tout(溢出时间)=(ARR+1)(PSC+1)/Tclk
6.7 定时器中断实验部分代码解读
6.7.1 timer.h头文件

#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
//申明一个定时器3中断初始化函数,入口参数包括16位的arr和psc//
void TIMER3_Interrput_Init(u16 arr, u16 psc);
#endif

6.7.2 timer.c文件

#include "timer.h"
#include "led.h"
//编写定时器3中断初始化函数,入口参数包括16位的arr和psc//
void TIMER3_Interrput_Init(u16 arr, u16 psc)
{
      //定义两个结构体//
      TIM_TimeBaseInitTypeDef TIM_TimeBaseInitTypeStruct;
      NVIC_InitTypeDef        NVIC_InitTypeDef_TIM3Struct;
      //使能TIM3时钟//
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
      //定时器参数初始化,第一个结构体参数初始化//
      TIM_TimeBaseInitTypeStruct.TIM_Period=arr;
      TIM_TimeBaseInitTypeStruct.TIM_Prescaler=psc;
      TIM_TimeBaseInitTypeStruct.TIM_CounterMode=TIM_CounterMode_Up;
      TIM_TimeBaseInitTypeStruct.TIM_ClockDivision=TIM_CKD_DIV1; 
      //*下面这行代码可以不写,值取0x00~0xFF之间,仅对TIM1和TIM8有效*//
      TIM_TimeBaseInitTypeStruct.TIM_RepetitionCounter=0x55;
      TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitTypeStruct);
      //*定时器3的中断使能//
      TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
      //*定时器3中断使能函数初始化*//
      NVIC_InitTypeDef_TIM3Struct.NVIC_IRQChannel=TIM3_IRQn;
      NVIC_InitTypeDef_TIM3Struct.NVIC_IRQChannelCmd=ENABLE;
      NVIC_InitTypeDef_TIM3Struct.NVIC_IRQChannelPreemptionPriority=0;
      NVIC_InitTypeDef_TIM3Struct.NVIC_IRQChannelSubPriority=3;
      NVIC_Init(&NVIC_InitTypeDef_TIM3Struct);
      //使能定时器3//
      TIM_Cmd(TIM3,ENABLE);
}
     //编写定时器3中断服务函数//
void TIM3_IRQHandler(void)
{   
 //获取定时器3状态//
 if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)//判断定时器3状态是否为上升模式//
 {
  LED1=!LED1;                               //是的话就翻转LED1//
  TIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清除标识//
 }
}

6.7.3 main.c文件

#include "sys.h"
#include "delay.h"
#include "led.h"
#include "timer.h"
 int main(void)
 { 
   //进行中断优先级分组//
   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  
   //初始化相关函数//  
   delay_init();
   LED_Init();
   TIMER3_Interrput_Init(4999,7199);//500ms触发一次中断//
  while(1)
  {
  }
 }

7. 实验二定时器PWM输出实验知识补充
7.1 PWM模式概念
在这里插入图片描述
7.2 PWM工作原理
在这里插入图片描述
如上图,ARR为重装载值,CCRx为某个捕获比较寄存器值,当CNT计数值小于CCRx时,可设输出低电平,反之输出高电平(脉宽调制信号,信号周期由ARR决定,占空比由CCRx决定)。
以通道1为例:
在这里插入图片描述
CCRx捕获比较(值)寄存器(x=1,2,3,4),用来设置比较值;
CCMR1: OC1M[2:0]位:对于PWM方式下,用于设置PWM模式1(110)或者PWM模式2(111);
CCER:CC1P位:输入/捕获1输出极性。0:高电平有效,1:低电平有效;
CCER:CC1E位:输入/捕获1输出使能。0:关闭,1:打开。
7.3 PWM模式1 & PWM模式2区别
针对上述寄存器TIMx_CCMR1的OC1M[2:0]位来分析:
在这里插入图片描述
PWM模式1:只要CNT小于CCRx,则为有效电平,反之为无效电平;
PWM模式2:只要CNT小于CCRx,则为无效电平,反之为有效电平。
在这里插入图片描述
7.4 自动重载的预装载寄存器
在这里插入图片描述

void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

简单的说:
RPE=1,ARR立即生效;
APRE=0,ARR下个比较周期生效。
7.5 STM32 定时器3输出通道引脚(可参考数据手册)
在这里插入图片描述
7.6 相关库函数
7.6.1 PWM输出库函数

void TIM_OCxInit(TIM_TypeDef*
TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
typedef struct
{
//*主要用到注释的四个参数*//
uint16_t TIM_OCMode;          //PWM模式1或者模式2//
uint16_t TIM_OutputState;     //输出使能 OR失能//
uint16_t TIM_OutputNState;
uint16_t TIM_Pulse;           //比较值,写CCRx
uint16_t TIM_OCPolarity;      //比较输出极性//
uint16_t TIM_OCNPolarity; 
uint16_t TIM_OCIdleState;  
uint16_t TIM_OCNIdleState; 
} TIM_OCInitTypeDef;

举例:

TIM_OCInitStructure.TIM_OCMode= TIM_OCMode_PWM2;              //PWM模式2//
TIM_OCInitStructure.TIM_OutputState= TIM_OutputState_Enable;  //比较输出使能//
TIM_OCInitStructure. TIM_Pulse=100;
TIM_OCInitStructure.TIM_OCPolarity= TIM_OCPolarity_High;      //输出极性:TIM输出比较极性高//
TIM_OC2Init(TIM3,&TIM_OCInitStructure);                       //根据T指定的参数初始化外设TIM3 OC2//

7.6.2 设置比较值函数:

void TIM_SetCompareX(TIM_TypeDef* TIMx, uint16_t Compare2);

7.6.3 使能输出比较预装载:

void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);

7.6.4 使能自动重装载的预装载寄存器允许位:

void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

7.7 PWM输出配置步骤:
7.7.1
使能定时器3和相关IO口时钟:
使能定时器3时钟:

RCC_APB1PeriphClockCmd();

使能GPIOB时钟:

RCC_APB2PeriphClockCmd();

7.7.2 初始化IO口为复用功能输出。函数:GPIO_Init();

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    

7.7.3 开启AFIO时钟(因为要把PB5(对应LED0)作定时器的PWM输出引脚,所以要重映射配置),同时设置重映射。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); 

7.7.4 初始化定时器,ARR,PSC等:

TIM_TimeBaseInit();

7.7.5 初始化输出比较参数:

TIM_OC2Init();

7.7.6 使能预装载寄存器:

TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable); 

7.7.7 使能定时器:

TIM_Cmd();

7.7.8 不断改变比较值CCRx,达到不同的占空比效果:

TIM_SetCompare2();

7.8 定时器PWM输出实验部分代码解读
7.8.1 timer.h头文件代码解读

#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
//申明一个定时器3 PWM初始化函数,入口参数包括16位的arr和psc//
void TIMER3_PWM_Init(u16 arr, u16 psc);
#endif

7.8.2 timer.c文件代码解读

#include "timer.h"
#include "led.h"
//编写定时器3 PWM初始化函数,入口参数包括16位的arr和psc//
void TIMER3_PWM_Init(u16 arr, u16 psc)
{
      //定义三个结构体//
      GPIO_InitTypeDef GPIO_InitTypeStruct;
      TIM_TimeBaseInitTypeDef TIM_TimeBaseInitTypeStruct;
      TIM_OCInitTypeDef TIM_OCInitTypeStruct;
      //使能TIM3时钟,使能GPIOB时钟,使能AFIO时钟//
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE);     
      //初始化GPIOB5为复用推挽输出,50MHz//
      GPIO_InitTypeStruct.GPIO_Pin=GPIO_Pin_5;
      GPIO_InitTypeStruct.GPIO_Mode=GPIO_Mode_AF_PP;
      GPIO_InitTypeStruct.GPIO_Speed=GPIO_Speed_50MHz;
      GPIO_Init(GPIOB,&GPIO_InitTypeStruct);
      //*使能TIM3部分重映射*//
      GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); 
      //定时器3参数初始化//
      TIM_TimeBaseInitTypeStruct.TIM_Period=arr;
      TIM_TimeBaseInitTypeStruct.TIM_Prescaler=psc;
      TIM_TimeBaseInitTypeStruct.TIM_CounterMode=TIM_CounterMode_Up;
      TIM_TimeBaseInitTypeStruct.TIM_ClockDivision=TIM_CKD_DIV1; 
      //*下面这行代码可以不写,值取0x00~0xFF之间,仅对TIM1和TIM8有效*//
      TIM_TimeBaseInitTypeStruct.TIM_RepetitionCounter=0x55;
      TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitTypeStruct);
      //初始化输出比较参数//
      TIM_OCInitTypeStruct.TIM_OCMode=TIM_OCMode_PWM2;
      TIM_OCInitTypeStruct.TIM_OutputState=TIM_OutputState_Enable;
      TIM_OCInitTypeStruct.TIM_Pulse=100;
      TIM_OCInitTypeStruct.TIM_OCPolarity=TIM_OCPolarity_High;
      TIM_OC2Init(TIM3,&TIM_OCInitTypeStruct);
      //使能预装载寄存器//
      TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); 
      //使能定时器3//
      TIM_Cmd(TIM3,ENABLE);
}

7.8.3 main.c文件代码解读

#include "sys.h"
#include "delay.h"
#include "led.h"
#include "timer.h"
 int main(void)
 {  
  u16 led0pwmval=0;
  u8 dir=1; 
  delay_init();              //延时函数初始化//   
  LED_Init();                //LED端口初始化//
  TIMER3_PWM_Init(899,0);    //不分频,PWM频率=72000000/900=80Khz//
  while(1)
 {
   delay_ms(10);             //延迟10ms//
   if(dir)led0pwmval++;      //若dir为1,则led0pwmval加1//
   else led0pwmval--;        //若dir不为1,则led0pwmval减1//
   if(led0pwmval>300)dir=0;  //若led0pwmval大于300,则dir为0//
   if(led0pwmval==0)dir=1;   //若led0pwmval等于0,则dir为1//        
   TIM_SetCompare2(TIM3,led0pwmval);   //设置定时器3比较值,该比较值先由0升至300,再降至0,不断重复//
 }  
 }

旧知识点
1)复习如何新建工程模板,可参考STM32学习心得二:新建工程模板
2)复习基于库函数的初始化函数的一般格式,可参考STM32学习心得三:GPIO实验-基于库函数
3)复习寄存器地址,可参考STM32学习心得四:GPIO实验-基于寄存器
4)复习位操作,可参考STM32学习心得五:GPIO实验-基于位操作
5)复习寄存器地址名称映射,可参考STM32学习心得六:相关C语言学习及寄存器地址名称映射解读
6)复习时钟系统框图,可参考STM32学习心得七:STM32时钟系统框图解读及相关函数
7)复习如何对GPIO进行复用及重映射,可参考STM32学习心得十二:端口复用和重映射
8)复习延迟函数,可参考STM32学习心得九:Systick滴答定时器和延时函数解读
9)复习ST-LINK仿真器的参数配置,可参考STM32学习心得十:在Keil MDK软件中配置ST-LINK仿真器
10)复习ST-LINK调试方法,可参考STM32学习心得十一:ST-LINK调试原理+软硬件仿真调试方法
11)复习中断相关知识,可参考STM32学习心得十三:NVIC中断优先级管理
12)复习外部中断一般配置,可参考STM32学习心得十五:外部中断实验

发布了30 篇原创文章 · 获赞 4 · 访问量 5117

猜你喜欢

转载自blog.csdn.net/Leisure_ksj/article/details/105652011