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

记录一下,方便以后翻阅~
主要内容
1) 三种定时器分类及区别;
2) 通用定时器特点;
3) 通用定时器工作过程;
4) 实验一:定时器中断实验补充知识及部代码解读;
6) 实验二:定时器PWM输出实验补充知识及部分代码解读;
7) 实验三:定时器输入捕获实验补充知识及部分代码解读。
相关实验
实验一定时器中断实验:通过定时器中断配置,使用定时器3,每隔500ms触发一次中断,后中断服务函数中控制LED实现LED1状态取反;
实验二定时器PWM输出实验:使用定时器3的PWM功能,输出占空比可变的PWM波,用来驱动LED灯,从而达到LED0亮度由暗变亮,又从亮变暗。
实验三定时器输入捕获实验:测量一个信号的脉冲宽度,要求如下图,按键按下后为高电平,获取2~3高电平的持续时间。
在这里插入图片描述
官方资料:官方资料:《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)
数器从自动装入的值 (TIMx_ARR计数器的值)开始向下计数到0,然后从自动装入的值重新开始并且产生一个计数器向下溢出的事件。
在这里插入图片描述
6.2.2 向上计数(时钟分频因子=1)
计数器从0计数到自动加载值(TIMx_ARR计数器的值),然后重新从0开始计数并且产生一个计数器溢出事件。
在这里插入图片描述
6.2.3 中央对齐计数模式(时钟分频因子=1 ARR=6)
计数器从0开始计数到自动加载的值(TIMx_ARR寄存器的值−1),产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器下溢事件,然后再从0开始重新计数。
在这里插入图片描述
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 编写中断服务函数:

void TIMx_IRQHandler(void);

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工作原理
脉冲宽度调制(PWM——Pulse Width Modulation)简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种技术。简单一点,就是对脉冲宽度的控制。
PWM工作逻辑可参考下图理解:
在这里插入图片描述

上图中,
ARR为自动重装载寄存器(TIMx_ARR)的位[0:15]设定值,
CCRx为某个捕获比较寄存器(TIMx_CRRx)的位[0:15]设定值,
黑线为计数器寄存器(TIMx_CNT)的位[0:15]实时值,记CNT。
当CNT小于CCRx时,可设输出低电平,反之输出高电平。因此可以认为:脉宽调制(PWM)信号的周期由ARR决定,占空比由CCRx决定。
在这里插入图片描述
以通道1为例,寄存器主要包括:
1) 捕获比较(值)寄存器(TIMx_CCRx(x=1,2,3,4)),用来设置比较值;
2) 捕获/比较模式寄存器 1(TIMx_CCMR1),位[4:6],输出比较1模式(OC1M):
在PWM方式下,设置PWM模式1(110),在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1);
或在PWM方式下,设PWM模式2(111),在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平(OC1REF=1),否则为无效电平(OC1REF=0)。
3) 捕获/比较使能寄存器(TIMx_CCER),位1,输入/捕获1输出极性CC1P:
当CC1通道配置为输出时,0:高电平有效,1:低电平有效;
4) 捕获/比较使能寄存器(TIMx_CCER),位0,输入/捕获1输出使能CC1E:
当CC1通道配置为输出时,0:关闭,1:打开。
7.3 PWM模式1 & PWM模式2区别(参考7.2)
针对上述寄存器TIMx_CCMR1的OC1M[2:0]位来分析:
PWM模式1:只要CNT小于CCRx,则为有效电平,反之为无效电平;
PWM模式2:只要CNT小于CCRx,则为无效电平,反之为有效电平。
举例如下图:
在这里插入图片描述
7.4 对自动重载的预装载寄存器的理解
自动装载寄存器是预先装载的,写或读自动重装载寄存器将访问预装载寄存器。根据在TIMx_CR1寄存器中的自动装载预装载使能位(ARPE)的设置,预装载寄存器的内容被立即或在每次的更新事件UEV时传送到影子寄存器。
影子寄存器保存的是定时器当前的计数值(或者溢出值),这个值是立即生效的值,这个计数值是从预装载寄存器(ARR)传过来的,但ARR什么时候把计数值传给影子寄存器呢?这儿就有个预装载使能位(ARPE):当ARPE=0的时候,你写入ARR的值马上就传到影子寄存器,也就立即生效当ARPE=1的时候,ARR的值就是直接传过去了,而是等到定时器更新事件发生,才把这个值传到影子寄存器,也就起到一个缓冲作用。
举例说明:
在这里插入图片描述
上图(左)所示,当ARPE=1时,自动加载寄存器值从F5改为36时,计数器寄存器值从F0增加至F5时产生更新事件,说明自动加载寄存器值从F5改为36时就已生效;
上图(右)所示,当ARPE=0时,自动加载寄存器值从FF改为36时,计数器寄存器值从31增加至36时产生更新事件,说明自动加载寄存器值从F5改为36时未即使生效,要等下个比较周期生效。
简单的说:
RPE=1,ARR立即生效;
APRE=0,ARR下个比较周期生效。
相关函数:

void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

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输出配置步骤(以TIM3_CH2为例):
7.7.1
使能定时器3和相关IO口时钟:
使能定时器3时钟:

void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);

使能GPIOB时钟:

void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);

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); //部分重映射,即TIM3_CH2对应PB5引脚//

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

void TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitTypeStruct);
//TIM_TimeBaseInitTypeStruct结构体包括5个参数://
//uint16_t TIM_Prescaler;            针对预分频器寄存器(TIMx_PSC),值范围在0x0000到0xFFFF之间//
//uint16_t TIM_CounterMode;          计数模式,前面已讲//
//uint16_t TIM_Period;               针对自动重装载寄存器(TIMx_ARR),确定脉宽调制信号的周期,前面已讲//
//uint16_t TIM_ClockDivision;        针对控制寄存器1(TIMx_CR1)的位[8:9],设定时钟分频因子CKD,输入捕获用//
//uint8_t TIM_RepetitionCounter。    重复计数寄存器(TIMx_RCR),只有高级定时器才有//

7.7.5 初始化输出比较参数:

void TIM_OC2Init(TIM3,&TIM_OCInitTypeStruct);

7.7.6 使能预装载寄存器:

TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);  
//针对捕获/比较模式寄存器1(TIM3_CCMR1),位[11],设置OC2PE值// 

设计preload register和shadow register的好处是,所有真正需要起作用的寄存器(shadow register)可以在同一个时间(发生更新事件时)被更新为所对应的preload register的内容,这样可以保证多个通道的操作能够准确地同步。如果没有shadow register,或者preload register和shadow register是直通的,即软件更新preload register时,同时更新了shadow register,因为软件不可能在一个相同的时刻同时更新多个寄存器,结果造成多个通道的时序不能同步,如果再加上其它因素(例如中断),多个通道的时序关系有可能是不可预知的。
7.7.7 使能定时器:

void TIM_Cmd(TIM3, Enable);

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

void TIM_SetCompare2(TIM3, uint16_t Compare2); //针对捕获/比较寄存器2(TIM3_CCR2),位[0:15],设置捕获/比较2(CCR2)的值//

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,不断重复//
 }  
 }

8. 实验三:定时器输入捕获实验知识补充
8.1 如下图,对应是左下部分内容。
在这里插入图片描述上图红框部分细节展开:
在这里插入图片描述
以通道1为例:
一句话总结工作过程:通过检测TIMx_CHx上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。
具体步骤为:
8.1.1 设置输入捕获滤波器;
在这里插入图片描述
涉及相关寄存器:
TIMx_CR1控制寄存器1,[9:8]位CKD时钟分频因子确定fDTS频率;
TIMx_CCMR1捕获/比较寄存器1,输入捕获模式下,[7:4]位的IC1F输入捕获1滤波器,确定采样频率及数字滤波器长度。
8.1.2 设置输入捕获极性;
在这里插入图片描述
涉及相关寄存器:
TIMx_CCER捕获/比较使能寄存器,输入捕获模式下,[1]位CC1P输入/捕获1输出极性,确定选择是IC1还是IC1的反相信号作为触发或捕获信号。
8.1.3 设置输入捕获映射通道;
在这里插入图片描述
涉及相关寄存器:
TIMx_CCMR1捕获/比较寄存器1,[1:0]位CC1S捕获/比较1选择,定义通道的方向及输入脚的选择。
8.1.4 设置输入捕获分频器;
在这里插入图片描述
涉及相关寄存器:
TIMx_CCMR1捕获/比较寄存器1,输入捕获模式下,[3:2]位IC1PSC输入/捕获1预分频器,定义CC1输入(IC1)的预分频系数;
TIMx_CCER捕获/比较使能寄存器,输入捕获模式下,[0]位CC1E输入/捕获1输出使能,决定计数器的值是否能捕获TIMx_CCR1寄存器。
8.1.5 捕获到有效信号可以开启中断;
在这里插入图片描述
涉及相关寄存器:
TIMx_DIER DMA/中断使能寄存器
8.1.6 最后,看看定时器通道对应引脚TIM5为例。
在这里插入图片描述
8.2 相关库函数

8.2.1 输入捕获通道初始化函数:

void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
typedef struct
{
uint16_t TIM_Channel;      //捕获通道1-4//   
uint16_t TIM_ICPolarity;   //捕获极性//
uint16_t TIM_ICSelection;  //映射关系//
uint16_t TIM_ICPrescaler;  //分频系数//
uint16_t TIM_ICFilter;     //滤波器//
}
TIM_ICInitTypeDef;   

举例:

TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; 
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM5_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM5, &TIM5_ICInitStructure);

8.2.2 通道极性设置独立函数:

void TIM_OCxPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity)

8.2.3 获取通道捕获值

uint32_t TIM_GetCapture1(TIM_TypeDef* TIMx)

8.3 输入捕获的一般配置步骤
8.3.1 初始化定时器和通道对应IO的时钟;
8.3.2 初始化IO口,模式为输入,

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;  //PA0 输入//

8.3.3 初始化定时器,主要设置ARR,PSC的值:

void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)

8.3.4 初始化输入捕获通道:

void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct)

8.3.5 如果要开启捕获中断:

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

8.3.6 使能定时器:

void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)

8.3.7 编写中断服务函数:

void TIMx_IRQHandler(void);

8.4 定时器输入捕获实验部分代码解读
8.4.1 timer.h文件代码解读

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

8.4.2 timer.c文件代码解读,本代码将顺便解读库函数相关指令对应的寄存器操作。

#include "timer.h"
//编写定时器5 输入捕获初始化函数,入口参数包括16位的arr和psc//
void TIM5_Cap_Init(u16 arr,u16 psc)
{  
 //申明GPIO,定时器时基,定时器输入捕获和中断参数初始化四个结构体//
 GPIO_InitTypeDef         GPIO_InitStructure;
 TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
 TIM_ICInitTypeDef        TIM5_ICInitStructure;
 NVIC_InitTypeDef         NVIC_InitStructure; 
 //**********************************************************//
 //使能TIM5时钟,使能GPIOA时钟//
 //针对RCC_APB1ENR寄存器,[3]位,TIM5EN设为1,即定时器5时钟开启//
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,  ENABLE); 
 //针对RCC_APB2ENR寄存器,[2]位,IOPAEN设为1,即IO端口A时钟开启//
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); 
 //**********************************************************//
 //初始化GPIOA,PA0,下拉输入模式//
 GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;    //选择引脚0 
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //下拉输入
 //按照上述参数初始化GPIOA//
 GPIO_Init(GPIOA, &GPIO_InitStructure);
 //PA0至0,PA0对应WK_UP按键,按下给1电平,未按给0电平// 
 GPIO_ResetBits(GPIOA,GPIO_Pin_0);        
 //**************************************************************//
 //初始化定时器5的间基结构//
 //针对TIM5_ARR寄存器,[0:15]位,取arr值,设定自动重装载寄存器的数值//
 TIM_TimeBaseStructure.TIM_Period = arr; 
 //针对TIM5_PSC寄存器,[0:15]位,取psc值,设定预分频器的值//
 TIM_TimeBaseStructure.TIM_Prescaler =psc;   
 //针对TIM5_CR1寄存器,[8:9]位,CKD设00,TDTS = Tck_tim//
 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;     
 //针对TIM5_CR1寄存器,[4]位,DIR设0,向上技术模式;[5:6]位,CMS设00,数器依据方向位(DIR)向上或向下计数//
 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
 //按照上述参数初始化定时器5的时基结构//
 TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);   
 //**************************************************************************************//
 //初始化TIM5输入捕获参数//
 //选择通道1//
 TIM5_ICInitStructure.TIM_Channel     = TIM_Channel_1;    
 //针对TIM5_CCMR1寄存器,输入模式下,[0:1]位,CC1S设为01,即CC1通道配置为输入,IC1映射到TI1上// 
 TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;  
 //针对TIM5_CCMR1寄存器,输入模式下,[4:7]位,IC1F设为0000,即IC1F设为无滤波器//
 TIM5_ICInitStructure.TIM_ICFilter = 0x00; 
 //针对TIM5_CCMR1寄存器,输入模式下,[2:3]位,IC1PSC设为00,不分频//
 TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;             
 //针对TIM5_CCER寄存器,输入模式下,[1]位,CC1E设为0,即上升沿捕获//
 TIM5_ICInitStructure.TIM_ICPolarity  = TIM_ICPolarity_Rising;  
 //按照上述参数初始化TIM5输入捕获//
 TIM_ICInit(TIM5, &TIM5_ICInitStructure);
 //*****************************************************************************//
 //TIM5中断参数初始化//
 NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;                  //选择TIM5中断//
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;        //抢占优先级2级//
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;               //从优先级0级//
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                  //IRQ通道被使能//
 NVIC_Init(&NVIC_InitStructure); 
 //****************************************************************************//
 //开启捕获中断//
 //针对TIM5_DIER寄存器,[0]位,UIE至1,允许更新中断;[1]位,CC1IE至1,允许捕获/比较1中断//
 TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);   
 //针对TIM5_CR1寄存器,[0]位,CEN至1,定时器5使能// 
 TIM_Cmd(TIM5,ENABLE );                                            
}
//****************************************************************************//
//下面开始编写定时器5中断服务函数//
u8  TIM5CH1_CAPTURE_STA=0; //定义一个8位的输入捕获状态变量,初始化值为0000 0000//          
u16 TIM5CH1_CAPTURE_VAL;   //定义一个16位输入捕获变量//
void TIM5_IRQHandler(void)
{ 
 //判断TIM5CH1_CAPTURE_STA的第7位值是否为0,即第7位为捕获完成标志//
 //如果TIM5CH1_CAPTURE_STA的第7位值为0,则捕获未完成,if返回1,执行下述括号内代码,否则跳至最后一行代码//
 if((TIM5CH1_CAPTURE_STA&0X80)==0)       
 {      
  //更新中断函数代码,为计数器溢出时服务//
  //判断定时器5的更新中断是否发生//
  //针对TIM5_DIER寄存器,[0]位,获取UIE更新中断使能状态值;针对TIM5_SR寄存器,[0]位,获取UIF更新中断标记值//
  //当UIE为1,UIF为1,说明定时器5产生更新事件,if返回值为1,执行下述括号内代码,否则跳至捕获中断代码//
  if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)   
  {    
   //判断TIM5CH1_CAPTURE_STA的第6位值是否为1,即发生更新中断之前,是否已捕获到上升沿了// 
   //如果TIM5CH1_CAPTURE_STA的第6位值为1,执行下述括号内代码,否则跳至捕获中断代码//     
   if(TIM5CH1_CAPTURE_STA&0X40)       
   {   
    //判断TIM5CH1_CAPTURE_STA的第0~5位值是否为0x3F,即判断溢出次数是否达到上限//
    //如果TIM5CH1_CAPTURE_STA的第0~5位值为0x3F,执行下述括号内代码,否则跳至下述else代码//
    if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)    
    {  
     //强制输出捕获的最大时间//
     //将TIM5CH1_CAPTURE_STA的第7位值至1,将TIM5CH1_CAPTURE_VAL设为0xFFFF//
     TIM5CH1_CAPTURE_STA|=0X80;
     TIM5CH1_CAPTURE_VAL=0XFFFF;
    }
    //如果TIM5CH1_CAPTURE_STA的第0~5位值不为0x3F,则执行下述else代码,TIM5CH1_CAPTURE_STA值加1//
    else TIM5CH1_CAPTURE_STA++;
   }  
  }
 //捕获中断函数代码,表示捕获到上升沿或下降沿//
 //判断定时器5的捕获1中断是否发生//
 //针对TIM5_DIER寄存器,[1]位,获取CC1IE捕获/比较1使能状态值;针对TIM5_SR寄存器,[1]位,获取CC1IF捕获标记值//
 //若捕获1发生捕获事件,即捕获1中断发生,则执行下述括号内代码,否则跳至最后一行代码//
 if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET) 
  { 
   //判断TIM5CH1_CAPTURE_STA的第6位值是否为1,即判断再此之前是否已捕获到上升沿了//
   //若TIM5CH1_CAPTURE_STA的第6位值为1,则之前以捕获到上升沿,本次是下降沿,即完成一次捕获//    
   if(TIM5CH1_CAPTURE_STA&0X40)      
   {    
    //将TIM5CH1_CAPTURE_STA的第7位值至1//
    TIM5CH1_CAPTURE_STA|=0X80;                        
    //将TIM_GetCapture1(TIM5)函数的返回值赋给TIM5CH1_CAPTURE_VAL//
    //针对TIM5_CCR1,获取[0:15]位的值并传至TIM5CH1_CAPTURE_VAL//
    TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5); 
    //重新配置极性,为上升沿捕获//    
    TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising);     //CC1P=0 设置为上升沿捕获//
   }
   else                                                    //本次捕获的是上升沿//
   {
    TIM5CH1_CAPTURE_STA=0;                              
    TIM5CH1_CAPTURE_VAL=0;
    //针对TIM5_CNT计数器寄存器,[0:15]位,全设0//
    TIM_SetCounter(TIM5,0);
    TIM5CH1_CAPTURE_STA|=0X40;                             //标记捕获到了上升沿//
    TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);    //CC1P=1 设置为下降沿捕获//
   }      
  }                     
  } 
    TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}

8.4.3 main.c文件代码解读

#include "led.h"
#include "key.h"
#include "timer.h"
#include "sys.h"
#include "delay.h"
#include "usart.h"
//下述两个变量已在timer.c文件中申明过//
extern u8  TIM5CH1_CAPTURE_STA;                            //输入捕获状态          
extern u16 TIM5CH1_CAPTURE_VAL;                            //输入捕获值 
int main(void)
 {  
 u32 temp=0; 
 //设置NVIC中断分组2,即2位抢占优先级,2位响应优先级//
 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);            
 delay_init();                                             //延时函数初始化    
 uart_init(115200);                                        //串口初始化为115200
 LED_Init();                                               //LED端口初始化 
 //初始化TIM5_Cap_Init(u16 arr,u16 psc)函数,arr=0xFFFF,psc=71,即以1Mhz的频率进行计数//
 //1Mhz计算来源:下述函数中,有针对TIM5_CR1寄存器[8:9]位的配置,即CKD设00,即CK_INT频率与CK_PSC频率一样//
 //CK_PSC频率除以psc+1=72,即获得CK_CNT=1Mhz,为什么+1请参考STM32中文参考手册,14.4.11预分频器TIMx_PSC//
 TIM5_Cap_Init(0XFFFF,72-1);  
  while(1)  
 {
   delay_ms(10);                                       //延时10ms//  
   //判断TIM5CH1_CAPTURE_STA的第7位值是否为0,0x80即0b1000 0000//
   //如果TIM5CH1_CAPTURE_STA的第7位值为0,则if返回1,即成功捕获一次上升沿和一次下降沿,执行下述括号内代码//  
   if(TIM5CH1_CAPTURE_STA&0X80)                            
  {
   //将TIM5CH1_CAPTURE_STA的第[0:5]位数据传至temp中//
   temp=TIM5CH1_CAPTURE_STA&0X3F;                      
   temp*=65536;                                        //temp的最小单位对应65536个计数时间总和//
   temp+=TIM5CH1_CAPTURE_VAL;                          //TIM5CH1_CAPTURE_VAL的最小单位对应1个计数时间//   
   printf("HIGH:%d us\r\n",temp);                      //打印总的高电平时间//
   TIM5CH1_CAPTURE_STA=0;                              //开启下一次捕获//
  }
 }
 }

8.5 定时器输入捕获实验代码缺点,从timer.c文件中也可以发现,每次捕获的上升沿至下降沿的时间间隔不能大于某个最大值(大概4秒多),否则将强制输出最大时间,并开始新一轮上升沿捕获。
旧知识点
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学习心得十五:外部中断实验

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天亮继续睡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值