你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

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

[复制链接]
STMCU小助手 发布时间:2022-11-21 18:00
主要内容:
1) 三种定时器分类及区别;
2) 通用定时器特点;
3) 通用定时器工作过程;
4) 实验一:定时器中断实验补充知识及部代码解读;
6) 实验二:定时器PWM输出实验补充知识及部分代码解读;
7) 实验三:定时器输入捕获实验补充知识及部分代码解读。
相关实验:
实验一定时器中断实验:通过定时器中断配置,使用定时器3,每隔500ms触发一次中断,后中断服务函数中控制LED实现LED1状态取反;
实验二定时器PWM输出实验:使用定时器3的PWM功能,输出占空比可变的PWM波,用来驱动LED灯,从而达到LED0亮度由暗变亮,又从亮变暗。
实验三定时器输入捕获实验:测量一个信号的脉冲宽度,要求如下图,按键按下后为高电平,获取2~3高电平的持续时间。

20200426082550873.jpg

1. STM32定时器
STM32F103RC和STM32F103ZE总共最多有8个定时器。

2020042110193945.png

2. 三种定时器区别

20200421102200424.png

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开始重新计数。

20200421102824773.png

5. 通用定时器的工作过程

20200421103305899.png
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细讲:

2020042111093244.png

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 内部时钟选择过程

2020042111124118.png

AHB时钟经APB1预分频后产生的时钟供通用定时器TIM2,3,4,5用,注意:如果APB1预分频=1,则1倍输出,否则2倍输出。

20200421111704228.png

举例:
再次强调:除非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,然后从自动装入的值重新开始并且产生一个计数器向下溢出的事件。

20200421112547398.png

6.2.2 向上计数(时钟分频因子=1)
计数器从0计数到自动加载值(TIMx_ARR计数器的值),然后重新从0开始计数并且产生一个计数器溢出事件。

20200421112617134.png

6.2.3 中央对齐计数模式(时钟分频因子=1 ARR=6)
计数器从0开始计数到自动加载的值(TIMx_ARR寄存器的值−1),产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器下溢事件,然后再从0开始重新计数。

20200421112634256.png

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 定时器参数初始化:

  1. void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
复制代码

举例:

  1. typedef struct
  2. {
  3. uint16_t TIM_Prescaler;        
  4. uint16_t TIM_CounterMode;     
  5. uint16_t TIM_Period;        
  6. uint16_t TIM_ClockDivision;  
  7. uint8_t  TIM_RepetitionCounter;
  8. } TIM_TimeBaseInitTypeDef;

  9. TIM_TimeBaseStructure.TIM_Period = 4999;
  10. TIM_TimeBaseStructure.TIM_Prescaler =7199;
  11. TIM_TimeBaseStructure.TIM_ClockDivision =   TIM_CKD_DIV1;
  12. TIM_TimeBaseStructure.TIM_CounterMode =   TIM_CounterMode_Up;
  13. TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
复制代码

6.4.2 定时器使能函数:

  1. void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
复制代码

6.4.3 定时器中断使能函数:

  1. void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
复制代码

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

  1. FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
  2. void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
  3. ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
  4. void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
复制代码

6.5 定时器中断实现步骤
6.5.1 使能定时器时钟:
  1. RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
复制代码

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

  1. TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
复制代码

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

  1. void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
  2. NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
复制代码

6.5.4 使能定时器:

  1. TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
复制代码

6.5.5 编写中断服务函数:

  1. void TIMx_IRQHandler(void);
复制代码

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

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

6.7.2 timer.c文件

  1. #include "timer.h"
  2. #include "led.h"
  3. //编写定时器3中断初始化函数,入口参数包括16位的arr和psc//
  4. void TIMER3_Interrput_Init(u16 arr, u16 psc)
  5. {
  6.       //定义两个结构体//
  7.       TIM_TimeBaseInitTypeDef TIM_TimeBaseInitTypeStruct;
  8.       NVIC_InitTypeDef        NVIC_InitTypeDef_TIM3Struct;
  9.       //使能TIM3时钟//
  10.       RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
  11.       //定时器参数初始化,第一个结构体参数初始化//
  12.       TIM_TimeBaseInitTypeStruct.TIM_Period=arr;
  13.       TIM_TimeBaseInitTypeStruct.TIM_Prescaler=psc;
  14.       TIM_TimeBaseInitTypeStruct.TIM_CounterMode=TIM_CounterMode_Up;
  15.       TIM_TimeBaseInitTypeStruct.TIM_ClockDivision=TIM_CKD_DIV1;
  16.       //*下面这行代码可以不写,值取0x00~0xFF之间,仅对TIM1和TIM8有效*//
  17.       TIM_TimeBaseInitTypeStruct.TIM_RepetitionCounter=0x55;
  18.       TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitTypeStruct);
  19.       //*定时器3的中断使能//
  20.       TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
  21.       //*定时器3中断使能函数初始化*//
  22.       NVIC_InitTypeDef_TIM3Struct.NVIC_IRQChannel=TIM3_IRQn;
  23.       NVIC_InitTypeDef_TIM3Struct.NVIC_IRQChannelCmd=ENABLE;
  24.       NVIC_InitTypeDef_TIM3Struct.NVIC_IRQChannelPreemptionPriority=0;
  25.       NVIC_InitTypeDef_TIM3Struct.NVIC_IRQChannelSubPriority=3;
  26.       NVIC_Init(&NVIC_InitTypeDef_TIM3Struct);
  27.       //使能定时器3//
  28.       TIM_Cmd(TIM3,ENABLE);
  29. }
  30.      //编写定时器3中断服务函数//
  31. void TIM3_IRQHandler(void)
  32. {   
  33. //获取定时器3状态//
  34. if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)//判断定时器3状态是否为上升模式//
  35. {
  36.   LED1=!LED1;                               //是的话就翻转LED1//
  37.   TIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清除标识//
  38. }
  39. }
复制代码

6.7.3 main.c文件

  1. #include "sys.h"
  2. #include "delay.h"
  3. #include "led.h"
  4. #include "timer.h"
  5. int main(void)
  6. {
  7.    //进行中断优先级分组//
  8.    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  
  9.    //初始化相关函数//  
  10.    delay_init();
  11.    LED_Init();
  12.    TIMER3_Interrput_Init(4999,7199);//500ms触发一次中断//
  13.   while(1)
  14.   {
  15.   }
  16. }
复制代码

7. 实验二:定时器PWM输出实验知识补充
7.1 PWM模式概念

20200421164114620.png

7.2 PWM工作原理
脉冲宽度调制(PWM——Pulse Width Modulation)简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种技术。简单一点,就是对脉冲宽度的控制。
PWM工作逻辑可参考下图理解:

20200421164214370.png

上图中,
ARR为自动重装载寄存器(TIMx_ARR)的位[0:15]设定值,
CCRx为某个捕获比较寄存器(TIMx_CRRx)的位[0:15]设定值,
黑线为计数器寄存器(TIMx_CNT)的位[0:15]实时值,记CNT。
当CNT小于CCRx时,可设输出低电平,反之输出高电平。因此可以认为:脉宽调制(PWM)信号的周期由ARR决定,占空比由CCRx决定。

20200421164312890.png

以通道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,则为无效电平,反之为有效电平。
举例如下图:

20200421164533444.png

7.4 对自动重载的预装载寄存器的理解
自动装载寄存器是预先装载的,写或读自动重装载寄存器将访问预装载寄存器。根据在TIMx_CR1寄存器中的自动装载预装载使能位(ARPE)的设置,预装载寄存器的内容被立即或在每次的更新事件UEV时传送到影子寄存器。
影子寄存器保存的是定时器当前的计数值(或者溢出值),这个值是立即生效的值,这个计数值是从预装载寄存器(ARR)传过来的,但ARR什么时候把计数值传给影子寄存器呢?这儿就有个预装载使能位(ARPE):当ARPE=0的时候,你写入ARR的值马上就传到影子寄存器,也就立即生效当ARPE=1的时候,ARR的值就是直接传过去了,而是等到定时器更新事件发生,才把这个值传到影子寄存器,也就起到一个缓冲作用。
举例说明:

20200421164739138.jpg

上图(左)所示,当ARPE=1时,自动加载寄存器值从F5改为36时,计数器寄存器值从F0增加至F5时产生更新事件,说明自动加载寄存器值从F5改为36时就已生效;
上图(右)所示,当ARPE=0时,自动加载寄存器值从FF改为36时,计数器寄存器值从31增加至36时产生更新事件,说明自动加载寄存器值从F5改为36时未即使生效,要等下个比较周期生效。
简单的说:
RPE=1,ARR立即生效;
APRE=0,ARR下个比较周期生效。
相关函数:
  1. void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
复制代码

7.5 STM32 定时器3输出通道引脚(可参考数据手册)

2020042116485230.png

STM32 定时器输出通道引脚整理

20200529144431573.png

7.6 相关库函数
7.6.1 PWM输出库函数

  1. void TIM_OCxInit(TIM_TypeDef*
  2. TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
  3. typedef struct
  4. {
  5. //*主要用到注释的四个参数*//
  6. uint16_t TIM_OCMode;          //PWM模式1或者模式2//
  7. uint16_t TIM_OutputState;     //输出使能 OR失能//
  8. uint16_t TIM_OutputNState;
  9. uint16_t TIM_Pulse;           //比较值,写CCRx
  10. uint16_t TIM_OCPolarity;      //比较输出极性//
  11. uint16_t TIM_OCNPolarity;
  12. uint16_t TIM_OCIdleState;  
  13. uint16_t TIM_OCNIdleState;
  14. } TIM_OCInitTypeDef;
复制代码

举例:

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

7.6.2 设置比较值函数:

  1. void TIM_SetCompareX(TIM_TypeDef* TIMx, uint16_t Compare2);
复制代码

7.6.3 使能输出比较预装载:

  1. void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
复制代码

7.6.4 使能自动重装载的预装载寄存器允许位:
  1. void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
复制代码

7.7 PWM输出配置步骤(以TIM3_CH2为例):
7.7.1
使能定时器3和相关IO口时钟:
使能定时器3时钟:
  1. void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
复制代码

使能GPIOB时钟:

  1. void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
复制代码

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

  1. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   
复制代码

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

  1. RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
  2. GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //部分重映射,即TIM3_CH2对应PB5引脚//
复制代码

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

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

7.7.5 初始化输出比较参数:

  1. void TIM_OC2Init(TIM3,&TIM_OCInitTypeStruct);
复制代码

7.7.6 使能预装载寄存器:

  1. TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);  
  2. //针对捕获/比较模式寄存器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 使能定时器:
  1. void TIM_Cmd(TIM3, Enable);
复制代码

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

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

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

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

7.8.2 timer.c文件代码解读

  1. #include "timer.h"
  2. #include "led.h"
  3. //编写定时器3 PWM初始化函数,入口参数包括16位的arr和psc//
  4. void TIMER3_PWM_Init(u16 arr, u16 psc)
  5. {
  6.       //定义三个结构体//
  7.       GPIO_InitTypeDef GPIO_InitTypeStruct;
  8.       TIM_TimeBaseInitTypeDef TIM_TimeBaseInitTypeStruct;
  9.       TIM_OCInitTypeDef TIM_OCInitTypeStruct;
  10.       //使能TIM3时钟,使能GPIOB时钟,使能AFIO时钟//
  11.       RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
  12.       RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE);     
  13.       //初始化GPIOB5为复用推挽输出,50MHz//
  14.       GPIO_InitTypeStruct.GPIO_Pin=GPIO_Pin_5;
  15.       GPIO_InitTypeStruct.GPIO_Mode=GPIO_Mode_AF_PP;
  16.       GPIO_InitTypeStruct.GPIO_Speed=GPIO_Speed_50MHz;
  17.       GPIO_Init(GPIOB,&GPIO_InitTypeStruct);
  18.       //*使能TIM3部分重映射*//
  19.       GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
  20.       //定时器3参数初始化//
  21.       TIM_TimeBaseInitTypeStruct.TIM_Period=arr;
  22.       TIM_TimeBaseInitTypeStruct.TIM_Prescaler=psc;
  23.       TIM_TimeBaseInitTypeStruct.TIM_CounterMode=TIM_CounterMode_Up;
  24.       TIM_TimeBaseInitTypeStruct.TIM_ClockDivision=TIM_CKD_DIV1;
  25.       //*下面这行代码可以不写,值取0x00~0xFF之间,仅对TIM1和TIM8有效*//
  26.       TIM_TimeBaseInitTypeStruct.TIM_RepetitionCounter=0x55;
  27.       TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitTypeStruct);
  28.       //初始化输出比较参数//
  29.       TIM_OCInitTypeStruct.TIM_OCMode=TIM_OCMode_PWM2;
  30.       TIM_OCInitTypeStruct.TIM_OutputState=TIM_OutputState_Enable;
  31.       TIM_OCInitTypeStruct.TIM_Pulse=100;
  32.       TIM_OCInitTypeStruct.TIM_OCPolarity=TIM_OCPolarity_High;
  33.       TIM_OC2Init(TIM3,&TIM_OCInitTypeStruct);
  34.       //使能预装载寄存器//
  35.       TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
  36.       //使能定时器3//
  37.       TIM_Cmd(TIM3,ENABLE);
  38. }
复制代码


7.8.3 main.c文件代码解读

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

8. 实验三:定时器输入捕获实验知识补充
8.1 如下图,对应是左下部分内容。

20200426083532174.png

上图红框部分细节展开:

20200426083925753.png

以通道1为例:
一句话总结工作过程:通过检测TIMx_CHx上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。
具体步骤为:
8.1.1 设置输入捕获滤波器;

2020042609232275.jpg

涉及相关寄存器:
TIMx_CR1控制寄存器1,[9:8]位CKD时钟分频因子确定fDTS频率;
TIMx_CCMR1捕获/比较寄存器1,输入捕获模式下,[7:4]位的IC1F输入捕获1滤波器,确定采样频率及数字滤波器长度。
8.1.2 设置输入捕获极性;

20200426092334934.jpg

涉及相关寄存器:
TIMx_CCER捕获/比较使能寄存器,输入捕获模式下,[1]位CC1P输入/捕获1输出极性,确定选择是IC1还是IC1的反相信号作为触发或捕获信号。
8.1.3 设置输入捕获映射通道;

20200426092346755.jpg

涉及相关寄存器:
TIMx_CCMR1捕获/比较寄存器1,[1:0]位CC1S捕获/比较1选择,定义通道的方向及输入脚的选择。
8.1.4 设置输入捕获分频器;

20200426092358876.jpg

涉及相关寄存器:
TIMx_CCMR1捕获/比较寄存器1,输入捕获模式下,[3:2]位IC1PSC输入/捕获1预分频器,定义CC1输入(IC1)的预分频系数;
TIMx_CCER捕获/比较使能寄存器,输入捕获模式下,[0]位CC1E输入/捕获1输出使能,决定计数器的值是否能捕获TIMx_CCR1寄存器。

8.1.5 捕获到有效信号可以开启中断;

20200426092924751.png

涉及相关寄存器:
TIMx_DIER DMA/中断使能寄存器。

8.1.6 最后,看看定时器通道对应引脚TIM5为例。

20200426093028659.png

8.2 相关库函数

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

  1. void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
  2. typedef struct
  3. {
  4. uint16_t TIM_Channel;      //捕获通道1-4//   
  5. uint16_t TIM_ICPolarity;   //捕获极性//
  6. uint16_t TIM_ICSelection;  //映射关系//
  7. uint16_t TIM_ICPrescaler;  //分频系数//
  8. uint16_t TIM_ICFilter;     //滤波器//
  9. }
  10. TIM_ICInitTypeDef;   
复制代码

举例:

  1. TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1;
  2. TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
  3. TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
  4. TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
  5. TIM5_ICInitStructure.TIM_ICFilter = 0x00;
  6. TIM_ICInit(TIM5, &TIM5_ICInitStructure);
复制代码

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

  1. void TIM_OCxPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
复制代码

8.2.3 获取通道捕获值

  1. uint32_t TIM_GetCapture1(TIM_TypeDef* TIMx);
复制代码

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

  1. void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
  2. GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;  //PA0 输入//
复制代码

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

  1. void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
复制代码

8.3.4 初始化输入捕获通道:

  1. void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
复制代码

8.3.5 如果要开启捕获中断:

  1. void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState)
  2. void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
复制代码

8.3.6 使能定时器:

  1. void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
复制代码

8.3.7 编写中断服务函数:

  1. void TIMx_IRQHandler(void);
复制代码

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

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

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

  1. #include "timer.h"
  2. //编写定时器5 输入捕获初始化函数,入口参数包括16位的arr和psc//
  3. void TIM5_Cap_Init(u16 arr,u16 psc)
  4. {  
  5. //申明GPIO,定时器时基,定时器输入捕获和中断参数初始化四个结构体//
  6. GPIO_InitTypeDef         GPIO_InitStructure;
  7. TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  8. TIM_ICInitTypeDef        TIM5_ICInitStructure;
  9. NVIC_InitTypeDef         NVIC_InitStructure;
  10. //**********************************************************//
  11. //使能TIM5时钟,使能GPIOA时钟//
  12. //针对RCC_APB1ENR寄存器,[3]位,TIM5EN设为1,即定时器5时钟开启//
  13. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,  ENABLE);
  14. //针对RCC_APB2ENR寄存器,[2]位,IOPAEN设为1,即IO端口A时钟开启//
  15. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  16. //**********************************************************//
  17. //初始化GPIOA,PA0,下拉输入模式//
  18. GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;    //选择引脚0
  19. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //下拉输入
  20. //按照上述参数初始化GPIOA//
  21. GPIO_Init(GPIOA, &GPIO_InitStructure);
  22. //PA0至0,PA0对应WK_UP按键,按下给1电平,未按给0电平//
  23. GPIO_ResetBits(GPIOA,GPIO_Pin_0);        
  24. //**************************************************************//
  25. //初始化定时器5的间基结构//
  26. //针对TIM5_ARR寄存器,[0:15]位,取arr值,设定自动重装载寄存器的数值//
  27. TIM_TimeBaseStructure.TIM_Period = arr;
  28. //针对TIM5_PSC寄存器,[0:15]位,取psc值,设定预分频器的值//
  29. TIM_TimeBaseStructure.TIM_Prescaler =psc;   
  30. //针对TIM5_CR1寄存器,[8:9]位,CKD设00,TDTS = Tck_tim//
  31. TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;     
  32. //针对TIM5_CR1寄存器,[4]位,DIR设0,向上技术模式;[5:6]位,CMS设00,数器依据方向位(DIR)向上或向下计数//
  33. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  34. //按照上述参数初始化定时器5的时基结构//
  35. TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);   
  36. //**************************************************************************************//
  37. //初始化TIM5输入捕获参数//
  38. //选择通道1//
  39. TIM5_ICInitStructure.TIM_Channel     = TIM_Channel_1;   
  40. //针对TIM5_CCMR1寄存器,输入模式下,[0:1]位,CC1S设为01,即CC1通道配置为输入,IC1映射到TI1上//
  41. TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;  
  42. //针对TIM5_CCMR1寄存器,输入模式下,[4:7]位,IC1F设为0000,即IC1F设为无滤波器//
  43. TIM5_ICInitStructure.TIM_ICFilter = 0x00;
  44. //针对TIM5_CCMR1寄存器,输入模式下,[2:3]位,IC1PSC设为00,不分频//
  45. TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;            
  46. //针对TIM5_CCER寄存器,输入模式下,[1]位,CC1E设为0,即上升沿捕获//
  47. TIM5_ICInitStructure.TIM_ICPolarity  = TIM_ICPolarity_Rising;  
  48. //按照上述参数初始化TIM5输入捕获//
  49. TIM_ICInit(TIM5, &TIM5_ICInitStructure);
  50. //*****************************************************************************//
  51. //TIM5中断参数初始化//
  52. NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;                  //选择TIM5中断//
  53. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;        //抢占优先级2级//
  54. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;               //从优先级0级//
  55. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                  //IRQ通道被使能//
  56. NVIC_Init(&NVIC_InitStructure);
  57. //****************************************************************************//
  58. //开启捕获中断//
  59. //针对TIM5_DIER寄存器,[0]位,UIE至1,允许更新中断;[1]位,CC1IE至1,允许捕获/比较1中断//
  60. TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);   
  61. //针对TIM5_CR1寄存器,[0]位,CEN至1,定时器5使能//
  62. TIM_Cmd(TIM5,ENABLE );                                            
  63. }
  64. //****************************************************************************//
  65. //下面开始编写定时器5中断服务函数//
  66. u8  TIM5CH1_CAPTURE_STA=0; //定义一个8位的输入捕获状态变量,初始化值为0000 0000//         
  67. u16 TIM5CH1_CAPTURE_VAL;   //定义一个16位输入捕获变量//
  68. void TIM5_IRQHandler(void)
  69. {
  70. //判断TIM5CH1_CAPTURE_STA的第7位值是否为0,即第7位为捕获完成标志//
  71. //如果TIM5CH1_CAPTURE_STA的第7位值为0,则捕获未完成,if返回1,执行下述括号内代码,否则跳至最后一行代码//
  72. if((TIM5CH1_CAPTURE_STA&0X80)==0)      
  73. {      
  74.   //更新中断函数代码,为计数器溢出时服务//
  75.   //判断定时器5的更新中断是否发生//
  76.   //针对TIM5_DIER寄存器,[0]位,获取UIE更新中断使能状态值;针对TIM5_SR寄存器,[0]位,获取UIF更新中断标记值//
  77.   //当UIE为1,UIF为1,说明定时器5产生更新事件,if返回值为1,执行下述括号内代码,否则跳至捕获中断代码//
  78.   if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)   
  79.   {   
  80.    //判断TIM5CH1_CAPTURE_STA的第6位值是否为1,即发生更新中断之前,是否已捕获到上升沿了//
  81.    //如果TIM5CH1_CAPTURE_STA的第6位值为1,执行下述括号内代码,否则跳至捕获中断代码//     
  82.    if(TIM5CH1_CAPTURE_STA&0X40)      
  83.    {   
  84.     //判断TIM5CH1_CAPTURE_STA的第0~5位值是否为0x3F,即判断溢出次数是否达到上限//
  85.     //如果TIM5CH1_CAPTURE_STA的第0~5位值为0x3F,执行下述括号内代码,否则跳至下述else代码//
  86.     if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)   
  87.     {  
  88.      //强制输出捕获的最大时间//
  89.      //将TIM5CH1_CAPTURE_STA的第7位值至1,将TIM5CH1_CAPTURE_VAL设为0xFFFF//
  90.      TIM5CH1_CAPTURE_STA|=0X80;
  91.      TIM5CH1_CAPTURE_VAL=0XFFFF;
  92.     }
  93.     //如果TIM5CH1_CAPTURE_STA的第0~5位值不为0x3F,则执行下述else代码,TIM5CH1_CAPTURE_STA值加1//
  94.     else TIM5CH1_CAPTURE_STA++;
  95.    }  
  96.   }
  97. //捕获中断函数代码,表示捕获到上升沿或下降沿//
  98. //判断定时器5的捕获1中断是否发生//
  99. //针对TIM5_DIER寄存器,[1]位,获取CC1IE捕获/比较1使能状态值;针对TIM5_SR寄存器,[1]位,获取CC1IF捕获标记值//
  100. //若捕获1发生捕获事件,即捕获1中断发生,则执行下述括号内代码,否则跳至最后一行代码//
  101. if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)
  102.   {
  103.    //判断TIM5CH1_CAPTURE_STA的第6位值是否为1,即判断再此之前是否已捕获到上升沿了//
  104.    //若TIM5CH1_CAPTURE_STA的第6位值为1,则之前以捕获到上升沿,本次是下降沿,即完成一次捕获//   
  105.    if(TIM5CH1_CAPTURE_STA&0X40)      
  106.    {   
  107.     //将TIM5CH1_CAPTURE_STA的第7位值至1//
  108.     TIM5CH1_CAPTURE_STA|=0X80;                        
  109.     //将TIM_GetCapture1(TIM5)函数的返回值赋给TIM5CH1_CAPTURE_VAL//
  110.     //针对TIM5_CCR1,获取[0:15]位的值并传至TIM5CH1_CAPTURE_VAL//
  111.     TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);
  112.     //重新配置极性,为上升沿捕获//   
  113.     TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising);     //CC1P=0 设置为上升沿捕获//
  114.    }
  115.    else                                                    //本次捕获的是上升沿//
  116.    {
  117.     TIM5CH1_CAPTURE_STA=0;                              
  118.     TIM5CH1_CAPTURE_VAL=0;
  119.     //针对TIM5_CNT计数器寄存器,[0:15]位,全设0//
  120.     TIM_SetCounter(TIM5,0);
  121.     TIM5CH1_CAPTURE_STA|=0X40;                             //标记捕获到了上升沿//
  122.     TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);    //CC1P=1 设置为下降沿捕获//
  123.    }      
  124.   }                     
  125.   }
  126.     TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
  127. }
复制代码

8.4.3 main.c文件代码解读

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

8.5 定时器输入捕获实验代码缺点,从timer.c文件中也可以发现,每次捕获的上升沿至下降沿的时间间隔不能大于某个最大值(大概4秒多),否则将强制输出最大时间,并开始新一轮上升沿捕获。
————————————————
版权声明:天亮继续睡


收藏 评论0 发布时间:2022-11-21 18:00

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版