欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-13 17:09:11
...

记录一下,方便以后翻阅~
主要内容
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个定时器。
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
2. 三种定时器区别
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
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开始重新计数。
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
5. 通用定时器的工作过程
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
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细讲:
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
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 内部时钟选择过程
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
AHB时钟经APB1预分频后产生的时钟供通用定时器TIM2,3,4,5用,注意:如果APB1预分频=1,则1倍输出,否则2倍输出
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
举例:
再次强调:除非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)
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
6.2.2 向上计数(时钟分频因子=1)
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
6.2.3 *对齐计数模式(时钟分频因子=1 ARR=6)
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
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模式概念
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
7.2 PWM工作原理
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
如上图,ARR为重装载值,CCRx为某个捕获比较寄存器值,当CNT计数值小于CCRx时,可设输出低电平,反之输出高电平(脉宽调制信号,信号周期由ARR决定,占空比由CCRx决定)。
以通道1为例:
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
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]位来分析:
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
PWM模式1:只要CNT小于CCRx,则为有效电平,反之为无效电平;
PWM模式2:只要CNT小于CCRx,则为无效电平,反之为有效电平。
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
7.4 自动重载的预装载寄存器
STM32学习心得十八:通用定时器基本原理及相关实验代码解读

void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

简单的说:
RPE=1,ARR立即生效;
APRE=0,ARR下个比较周期生效。
7.5 STM32 定时器3输出通道引脚(可参考数据手册)
STM32学习心得十八:通用定时器基本原理及相关实验代码解读
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学习心得十五:外部中断实验