深入了解STM32定时器的功能与应用

STM32F103系列单片机定时器主要有:系统定时器SysTick,2个高级定时器TIM1和TIM8,4个通用定时器TIM2/3/4/5,2个基本定时器TIM6和TIM7。下面先简单介绍一下:

  1. 基本定时器:基本定时器只能计时,不能产生中断。它们适合于需要简单计时的应用程序。

  2. 通用定时器:通用定时器可以配置为计时器或者PWM发生器,并且可以产生中断。它们可以用于控制电机、LED灯等。

  3. 高级定时器:高级定时器有更多的功能,如输入捕获、输出比较和PWM发生器。它们适用于需要高精度计时或PWM控制的应用程序。

  4. 看门狗定时器:看门狗定时器可以监视系统是否正常工作,并在系统停止响应时重置系统。它们可以确保系统的稳定性和可靠性。

  5. SysTick定时器:可以作为系统节拍计时器,可以用于实现延时函数。

在我们基础的开发中,对于SysTick定时器、基本定时器、通用定时器、高级定时器的使用较多,因此我们展开详细描述。其中,通用定时器的功能在高级定时器中都存在,不做赘述。所有案例程序的源码附在文末,直接下载就可使用。

目录

SysTick定时器

一、结构图与寄存器

二、SysTick定时时间计算

三、Systick-系统定时器配置程序

 如何更改systick中断优先级:

四、实验设计

(1) 通过查询标志位来写延时函数

 (2)通过使能中断来写延时函数

基本定时器

一、功能框图

(1) 时钟源

(2) 控制器

(3) 时基单元

(4) 影子寄存器

二、定时时间的计算

三、时基初始化结构体

四、实验设计

(1) 配置时基初始化结构体

(2) 开启定时器更新中断(即定时时间到了)

(3) 编写main函数

高级定时器

一、功能框图

(1) 时钟源

(2) 时基单元

(3) 输入捕获

(4) 输出比较

二、输入捕获的应用

三、输出比较的应用

四、初始化结构体

(1) 时基初始化结构体

(2) 输出比较结构体

(3) PWM信号 周期和占空比的计算–以通用定时器为例

(4) 输入捕获结构体

(5) 断路和死区初始化结构体

五、程序设计

(1) 高级定时器定时功能设计

(2) 通用定时器PWM输出功能设计

文章案例程序源码下载

一、SysTick定时器的延时函数(毫秒、微秒)源码:

二、基本定时器LED反转设计源码:

三、高级定时器定时功能设计源码:

四、通用定时器PWM输出功能设计源码:


SysTick定时器

SysTick定时器简介

SysTick定时器,即系统定时器,24位,只能递减,存在于内核,嵌套在NVIC中。

所有的Cortex-M内核的单片机都具有这个定时器。通过系统定时器,我们可以实现精准的软件延时(毫秒、微秒级)。

一、结构图与寄存器

重装载寄存器:存放初始值   STK_CLK:时钟

counter在时钟的驱动下,从reload初值开始往下递减计数到0(这样为一个循环),产生中断和置位COUNTFLAG标志。然后又从reload值开始重新递减计数,如此循环。

其相关寄存器在M3编程手册里面:

1.STK_CTRL寄存器(控制及状态寄存器):第2位:AHB指的是高速总线时钟—HCLK,为72M,这一位置0,时钟就是9M,这一位置1,时钟就是72M。第1位:置0,不产生中断;置1,产生中断。

2. STK_LOAD寄存器(重装载数值寄存器),存放一个24位的初始值。

3. STK_VAL寄存器(当前数值寄存器)。

二、SysTick定时时间计算

首先,我们要确定3个变量。

  1-t:一个计数循环的时间(从初始值递减到0所用的时间),跟reload(存放初始值的寄存器)和CLK有关

  2-CLK:72M或者9M,由STK_CTRL寄存器配置

  3-RELOAD(初始值):24位,用户自己配置

t = reload * ( 1/clk )

Clk = 72M时,t = (72) *(1/ 72 M )= 1US

Clk = 72M时,t = (72000) *(1/ 72 M )= 1MS

三、Systick-系统定时器配置程序

SysTick寄存器结构体:在固件库文件:core_cm3.h中定义

SysTick配置库函数:在固件库文件:core_cm3.h中定义

SysTick配置中断优先级:在固件库文件:core_cm3.h中定义

SysTick配置的过程(此程序已经写好):

步骤如下:

 1.判断自己规定的初始值是否超过了最大值。

 2.初始化reload寄存器的值(确定初始值)。

 3.配置systick的中断优先级。

static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */
  /*ticks;自己规定的初始值       
 	  SysTick_LOAD_RELOAD_Msk:2的24次方  初始值超过了最大值,肯定是不行的
	*/
	/*初始化reload寄存器的值*/
  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */
  
	/*配置systick的中断优先级*/
	NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Cortex-M0 System Interrupts */
  
	/*读取当前计数的值,计数器值清0*/
	SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
	
	/*配置systick的时钟为AHB时钟,使能systick的中断,使能systick*/
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}

systick中断优先级配置的是scb->shprx寄存器(内核的);而外设的中断优先级配置的是nvic->iprx,有优先级分组,有抢占优先级和子优先级的说法。

 如何更改systick中断优先级

我们看一下中断优先级的第二个形参;:__NVIC_PRIO_BITS,官网给的备注是:STM32使用4位作为优先级,所以这个形参的官网给的值是4。在这个函数中,1左移4位,就是16,16-1=15。总共有4个位来表示优先级,15就是“1111”,也就是说,systick中断这个优先级永远是最低的。

比较内核和外设的中断优先级:自己先确定好外设的NVIC分组,systick就套用相同的分组方法。内核外设的中断优先级的四个位按照外设的中断优先级来分组来解析,即人为的分出抢占优先级和子优先级。例如:前面这个15,解析成4个位就为:1111。如果NVIC分组选择的是组1,即第一个1代表着主优先级,后面三个1代表着子优先级。这个时候,如果你想提高systick中断的优先级的话,就更改/子优先级的大小,比外设的主/子优先级高就行(也就是上面4位2进制的数变低)。

所以:我们只需要更改“__NVIC_PRIO_BITS”这个变量的大小即可。

4.读取当前计数的值,计数器值清0(VAL寄存器)。

5.配置systick的时钟为AHB时钟,使能systick的中断,使能systick。

四、实验设计

1-编写一个微秒延时函数  2-编写一个毫秒延时函数

方法有两种:通过查询标志位来写延时函数,通过使能中断来写延时函数

(1) 通过查询标志位来写延时函数

  先定义一个变量,循环一轮(1ms/us),然后在for循环里面等待到了我们规定的延时的时间(CTRL寄存器寄存器第16位为1),就完成了延时。

/*通过查询标志位来写延时函数*/
void SysTick_Delayms(uint32_t ms)//1毫秒延时函数,延时多少毫秒更改形参即可
{
	uint32_t i;
	
	SysTick_Config(72000);//循环1轮就是1毫秒
	
	for(i=0;i<ms;i++)
	{
		while(((SysTick->CTRL) & (1<<16)) != 1);
		//判断CTRL这个寄存器的16位不为1,就继续在while循环里面等待
	}
	
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;//把这位清0,失能systick
}

void SysTick_Delayus(uint32_t us)//1微秒延时函数,延时多少微秒更改形参即可
{
	uint32_t i;
	
	SysTick_Config(72);//循环1轮就是1微秒
	
	for(i=0;i<us;i++)
	{
		while(((SysTick->CTRL) & (1<<16)) != 1);
		//判断CTRL这个寄存器的16位不为1,就继续在while循环里面等待
	}
	
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;//把这位清0,失能systick
}

 (2)通过使能中断来写延时函数

  定义一个全局变量(毫秒和微秒要分开用),先把我们要延时的这个数值赋给这个全局变量,然后走一轮循环;在中断服务函数里面,找到systick的中断服务函数,每进一次中断这个全局变量就减一;建立while循环,减到了0之后失能systick。

中断函数文件内:

extern volatile uint32_t isr_ms;//这个全局变量在bsp_systick.c文件中
extern volatile uint32_t isr_us;//这个全局变量在bsp_systick.c文件中

void SysTick_Handler(void)//systick 的中断服务函数
{
	isr_ms--;
	isr_us--;
}

延时函数文件内:

/*通过使能中断来写延时函数*/
volatile uint32_t isr_ms;//定义一个全局变量

void SysTick_Delayms_INT(uint32_t ms)//1毫秒延时函数,延时多少毫秒更改形参即可
{
	isr_ms = ms;
	SysTick_Config(72000);//循环1轮就是1毫秒
	
	while(isr_ms);
	
	//把这位清0,失能systick
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}

volatile uint32_t isr_us;//定义一个全局变量

void SysTick_Delayus_INT(uint32_t us)//1毫秒延时函数,延时多少毫秒更改形参即可
{
	isr_us = us;
	SysTick_Config(72);//循环1轮就是1毫秒
	
	while(isr_us);
	
	//把这位清0,失能systick
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
}

 本程序源码见文章末尾:SysTick定时器的延时函数(毫秒、微秒)

基本定时器

基本定时器简介

计数器16bit,只能从0开始向上计数。没有外部的GPIO,是内部资源,只能用来定时。时钟来自PCLK1,为72M,可实现1~65536分频。

一、功能框图

(1) 时钟源

时钟源来自RCC的TIMx_CLK(属于内部的CK_INT)

基本定时器和通用定时器是挂载到APB1总线上,由于APB1的预分频系数是2,所以频率要×2,也就是72M。

(2) 控制器

控制器用于控制定时器的:复位、使能、计数、触发DAC。涉及到的寄存器为:CR1/2、DIER(DMA/中断使能寄存器)、EGR(事件产生寄存器)、SR(状态寄存器)

(3) 时基单元

定时器最主要的就是时基部分:包括预分频器、计数器、自动重装载寄存器。这些所有定时器都有。

预分频器PSC (寄存器 TIMx_PSC控制):

结构框图中:CK_PSC就是从APB1得到的时钟(72M),预分频控制寄存器的值就是PSC里面的值,而CK_CNT就是分频最终得到的值。

16位的预分频器PSC对内部时钟CK_PSC进行分频之后,得到计数器时钟CK_CNT=CK_PSC(预分频器时钟)/(PSC(预分频因子)+1)

计数器CNT在计数器时钟的驱动下开始计数,计数一次的时间为1/CK_CNT。

计数器、自动重装载寄存器

定时器使能(CEN置1)后,计数器 CNT在CK_CNT 驱动下计数,当TCNT值(计数器的值)与ARR 的设定值(自动重装载寄存器的值,自动重装载寄存器是16位的,最大值是65535)相等时就自动生成事件(如果使能中断就产生中断)并 CNT 自动清零,然后自动重新开始计数,如此重复以上过程。

(4) 影子寄存器

功能框图上有个影子,PSC和ARR都有影子寄存器。

什么是影子寄存器?

这表示在物理上这个寄存器对应2个寄存器:一个是我们可以可以写入或读出的寄存器,称为预装载寄存器,另一个是我们看不见的、无法真正对其读写操作的,但在使用中真正起作用的寄存器,称为影子寄存器。

每次发生更新事件(UEV)时,所有真正起作用的影子寄存器(shadowregister)可以同时被更新为预装载寄存器 (preloadregister)里面的值,这样可以保证多个通道的操作能够准确同步;如果没有影子寄存器(shadowregister),或者预装载寄存器 (preloadregister) 和影子寄存器 (shadowregister)是直通的,当软件更新了预装载寄存器 (preloadregister),也就立即更新了影子寄存器 (shadowregister),由于软件不能同时更新多个影子寄存器,这就会导致多个通道的时序不能同步。

原文链接:

https://blog.csdn.net/weixin_51703421/article/details/126500556

影子寄存器的存在起到一个缓冲的作用,用户值->寄存器->影子寄存器->起作用。例如:自动重装载的影子寄存器(ARR寄存器):自动重载寄存器是预装载的。对自动重载寄存器执行写入或读取操作时会访问预装载寄存器。预装载寄存器的内容既可以直接传送到影子寄存器,也可以在每次发生更新事件 (UEV) 时传送到影子寄存器。也就是说,在CNT计数器计数的过程中,自动重装载寄存器的值可以随时更改,更改的数值可以立刻起作用,也可以在本轮计数完成之后再起作用。是否立刻起作用可以通过软件来进行控制。由控制寄存器 1(TIMx_CR1)的ARPE位来控制。0:TIMx_ARR寄存器没有缓冲 1:TIMx_ARR寄存器具有缓冲。

而PSC的影子寄存器,只能在这一轮计数完成之后的下一轮才开始起作用。这个是不能通过软件来控制的

如果不使用影子寄存器则用户值在写到寄存器之后则里面起作用。

二、定时时间的计算

PSC = 72-1,定时器频率=72M/(PSC+1)=1MHZ,近似时间为1微秒。

若我们设定ARR(自动重装载寄存器)= 1000-1,从0计数到999,则计了1000次。

中断周期   T = 1000 *1/1000000 = 1ms

三、时基初始化结构体

时基初始化结构体 TIM_TimeBaseInitTypeDef 里面有5个成员,TIM6和TIM7的寄存器里面只有TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,另外三个成员是通用定时器和高级定时器才有。TIM_Prescaler对应预分频器(TIMx_PSC),TIM_Period对应自动重装载寄存器(TIMx_ARR)。

typedef struct
{ 
   TIM_Prescaler             都有
   TIM_CounterMode			 TIMx,x[6,7]没有,其他都有
   TIM_Period                都有
   TIM_ClockDivision         TIMx,x[6,7]没有,其他都有
   TIM_RepetitionCounter     TIMx,x[1,8,15,16,17]才有
}TIM_TimeBaseInitTypeDef; 

四、实验设计

基本定时器产生500MS定时,在主函数里面让LED反转

设计思路:让基本定时器产生1ms的中断,到了1ms之后进入中断服务函数,中断服务函数里的变量自加,加到500之后代表到了500ms。我们可以在主函数内部判断变量值是否等于500,到了500让LED翻转一次。

这个就与51单片机或者蓝桥杯单片机的定时器0、1、2的定时功能差不多,我们通过定时的功能,完成一个固定周期的重复事件。

(1) 配置时基初始化结构体

首先宏定义硬件,基本定时器的时钟,自动重装载的值,时钟分频因子,定时器的中断通道(在使用高级定时器的时候只更改这些即可)。

/********************基本定时器TIM参数定义,只限TIM6、7************/

#define BASIC_TIM6 // 如果使用TIM7,注释掉这个宏即可

#ifdef  BASIC_TIM6 // 使用基本定时器TIM6
#define            BASIC_TIM                   TIM6
#define            BASIC_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            BASIC_TIM_CLK               RCC_APB1Periph_TIM6
#define            BASIC_TIM_Period            1000-1 //自动重装载的值 1000次计数进一次中断,也就是1毫秒进一次中断
#define            BASIC_TIM_Prescaler         71     //时钟分频因子 控制计数一次的时间 72/(71+1) = 1M 也就是1微秒
#define            BASIC_TIM_IRQ               TIM6_IRQn //中断源 所有的中断源在 stm32f10x.h文件中
#define            BASIC_TIM_IRQHandler        TIM6_IRQHandler

#else  // 使用基本定时器TIM7
#define            BASIC_TIM                   TIM7
#define            BASIC_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            BASIC_TIM_CLK               RCC_APB1Periph_TIM7
#define            BASIC_TIM_Period            1000-1
#define            BASIC_TIM_Prescaler         71
#define            BASIC_TIM_IRQ               TIM7_IRQn
#define            BASIC_TIM_IRQHandler        TIM7_IRQHandler

#endif

配置、初始化时基结构体,配置、初始化NVIC结构体。

// 中断优先级配置
static void BASIC_TIM_NVIC_Config(void)//涉及到中断就要配置、初始化 NVIC
{
    NVIC_InitTypeDef NVIC_InitStructure; 
    // 设置中断组为0
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);		
		// 设置中断来源
    NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIM_IRQ ;	
		// 设置主优先级为 0
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;	 
	  // 设置抢占优先级为3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;	
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}


static void BASIC_TIM_Mode_Config(void)//初始化时基结构体
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;	
	
	// 开启定时器时钟,即内部时钟CK_INT=72M
    BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, ENABLE);
	
		// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
    TIM_TimeBaseStructure.TIM_Period = BASIC_TIM_Period;	

	  // 时钟预分频数为
    TIM_TimeBaseStructure.TIM_Prescaler= BASIC_TIM_Prescaler;
	
		// 时钟分频因子 ,基本定时器没有,不用管
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
		
		// 计数器计数模式,基本定时器只能向上计数,没有计数模式的设置
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; 
		
		// 重复计数器的值,基本定时器没有,不用管
		TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
	
	  // 初始化定时器
    TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure);
		
		// 清除计数器中断标志位
    TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update);
	  
		// 开启计数器中断
    TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE);
		
		// 使能计数器
    TIM_Cmd(BASIC_TIM, ENABLE);	
}

void BASIC_TIM_Init(void)
{
	BASIC_TIM_NVIC_Config();
	BASIC_TIM_Mode_Config();
}

(2) 开启定时器更新中断(即定时时间到了)

具体哪个定时器,具体哪种中断。这里用到的是更新中断,计数器从n计数到m为一轮产生的中断就叫做更新中断。

extern volatile uint32_t time;

void  BASIC_TIM_IRQHandler (void)
{
	if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET )//判断是否产生中断 
	{	
		time++;
		TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);  		 
	}		 	
}

(3) 编写main函数

int main(void)
{
	/*在程序来到main函数这里的时候,系统时钟已经配置成72M*/
	LED_GPIO_Config();//初始化函数
	BASIC_TIM_Init();
	
	while(1)
	{
		if(time == 500)
		{
			time = 0;		    
			LED1_TOGGLE;/* LED1 取反 实现LED1的亮灭反转*/  
		}
	}
}

本程序源码下载链接见文章末尾:基本定时器LED反转设计源码。

高级定时器

高级定时器简介

计数器16bit,可从上/下/两边计数,上:从0计数到用户配置的值;下:从用户配置的值倒数到0;两边:从0计数到用户配置的值,再从用户配置的值倒数到0。TIM1和TIM8,时基单元里还有一个重复计数器RCR,独有。

相比基本定时器,通用定时器和高级定时器有4个GPIO,其中通道1~3还有互补输出GPIO,加起来共有7个。

时钟来自PCLK2,为72M(所有的定时器都是72M),可实现1~65536分频。

一、功能框图

1-时钟源  2-控制器 3-时基 4-输入捕获 5-输出比较 6-断路功能

(1) 时钟源

内部时钟源

时钟源有四种模式,我们最常用的就是CK_INT:来自RCC的TIMx_CLK

高级定时器挂载到APB2总线上,APB2的预分频系数是1,所以不变,还是72M。

外部时钟模式1—外部的GPIO Tix(x=1 2 3 4)和外部时钟模式2—外部的GPIO ETR不常用。外部时钟模式1可以从外部的引脚(功能框图左侧的TIMx_CH1、2、3、4引脚)给计数器提供时钟;外部时钟模式2可以从外部的特殊引脚(功能框图左侧的TIMx_ETR引脚)给计数器提供时钟。

这些引脚对应哪些具体引脚,参考下图

外部时钟1:

虽然有4个引脚,但不能同时使用两个,只能使用1个。

①时钟信号输入引脚 外部的GPIO Tix,对应:TIMx_CH1/2/3/4,TIM_CCMRx寄存器的位CCxS[1:0]配置,其中CCMR1控制TI1/2,CCMR2控制TI3/4。

②滤波器 如果来自外部的时钟信号的频率过高或者混杂有高频干扰信号的话,我们就需要使用滤波器对ETRP信号重新采样,来达到降频或者去除高频干扰的目的,由TIMx_CCMRx寄存器的位ICxF[3:0]配置。

③边沿检测 边沿检测的信号来自于滤波器的输出,在成为触发信号之前,需要进行边沿检测,决定是上升沿有效还是下降沿有效。由TIMx_CCER寄存器的位CCxP和CCxNP 配置。

④触发选择 当使用外部时钟模式1时,触发源有两个,一个是滤波后的定时器输入1(TI1FP1)和滤波后的定时器输入2(TI2FP2)。由TIMx_SMCR寄存器的位TS[2:0]配置。

⑤从模式选择 选定了触发源信号后,最后我们需把信号连接到TRGI引脚,让触发信号成为外部时钟模式1的输入,最终等于 CK_PSC,然后驱动计数器CNT计数。具体的配置TIMx_SMCR寄存器的位SMS[2:0]为 000 即可选择外部时钟模式1。

经过上面的5个步骤之后, 最后我们只需使能计数器开始计数,外部时钟模式1的配置就算完成。使能计数器由TIMx_CR1寄存器的位CEN 配置。

外部时钟2

①时钟信号输入引脚 当使用外部时钟模式2的时候,时钟信号来自于定时器的特定输入通道TIMx_ETR,只有1个。

②外部触发极性 来自ETR引脚输入的信号可以选择为上升沿或者下降沿有效。具体的由TIMx_SMCR寄存器的位ETP 配置。

③外部触发预分频器 由于ETRP的信号的频率不能超过TIMx_CLK(180M)的 1/4,当触发信号的频率很高的情况下,就必须使用分频器来降频。具体的由TIMx_SMCR寄存器的位ETPS[1:0]配置。

④滤波器 如果ETRP的信号的频率过高或者混杂有高频干扰信号的,需要使用滤波器对ETRP信号重新采样,来达到降频或者去除高频干扰的目的。具体的由TIMx_SMCR寄存器的位ETF[3:0]配置,其中的fDTS是由内部时钟CK_INT分频得到,具体的由TIMx_CR1寄存器的位CKD[1:0]配置。

⑤从模式选择 经过滤波器滤波的信号连接到ETRF引脚后,触发信号成为外部时钟模式2的输入,最终等于CK_PSC,然后驱动计数器 CNT 计数。具体的配置TIMx_SMCR寄存器的位ECE为 1即可选择外部时钟模式2。

经过上面的5个步骤之后,最后我们只需使能计数器开始计数,外部时钟模式2的配置就算完成。使能计数器由TIMx_CR1寄存器的位CEN配置。

(2) 时基单元

预分频器 PSC

16位,由预分频器(TIMx_PSC)控制。有一个输入时钟CK_PSC和一个输出时钟CK_CNT。输入时钟CK_PSC就是上面时钟源的输出,输出CK_CNT则用来驱动计数器CNT计数。通过设置预分频器PSC的值可以得到不同的CK_CNT。计数器的时钟频率(CK_CNT)=fck_psc/( PSC[15:0]+1)

计数器CNT

16位,由计数器(TIMx_CNT)控制。

递增计数模式下,计数器从0开始计数,每来一个CK CNT脉冲计数器就增加1,直到计数器的值与自动重载寄存器ARR值相等,然后计数器又从0开始计数并生成计数器上溢事件,计数器总是如此循环计数。如果禁用重复计数器,在计数器生成上溢事件就马上生成更新事件(UEV);如果使能重复计数器,每生成一次上溢事件重复计数器内容就减1,直到重复计数器内容为0时才会生成更新事件。

递减计数模式下,计数器从自动重载寄存器ARR值开始计数,每来一个CK CNT脉冲计数器就减1,直到计数器值为0,然后计数器又从自动重载寄存器ARR值开始递减计数并生成计数器下溢事件,计数器总是如此循环计数。如果禁用重复计数器,在计数器生成下溢事件就马上生成更新事件:如果使能重复计数器,每生成一次下溢事件重复计数器内容就减1,直到重复计数器内容为0时才会生成更新事件。

中心对齐模式下,计数器从0开始递增计数,直到计数值等于(ARR-1)值生成计数器上溢事件,然后从ARR值开始递减计数直到1生成计数器下溢事件。然后又从0开始计数,如此循环。每次发生计数器上溢和下溢事件都会生成更新事件。

自动重装载寄存器ARR

16位,由自动重装载寄存器(TIMx_ARR)控制,与前面基本定时器的ARR基本相同。

重复计数器RCR

8位。由重复计数寄存器(TIMx_RCR)控制。基本/通用定时器,发生上/下溢事件时,直接生成更新事件。高级控制定时器多出了重复计数器。定时器发生上溢、下溢事件时,递减重复计数器的值。当重复计数器为0时,才会生成更新事件。发生N+1个上溢、下溢事件(N为RCR的值)时,产生更新事件。

(3) 输入捕获

输入捕获可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,常用的有测量输入信号的脉宽和测量PWM输入信号的频率和占空比这两种。

 

输入捕获的大概的原理就是,当捕获到信号的跳变沿(上升或者下降沿)的时候,把计数器CNT的值锁存到捕获寄存器CCR中,把前后两次捕获到的CCR寄存器中的值相减,就可以算出脉宽或者频率。如果捕获的脉宽的时间长度超过你的捕获定时器的周期,就会发生溢出,这个我们需要做额外的处理。

①输入通道 当使用需要被测量的信号从定时器的外部引脚 TIMx_CH1/2/3/4进入,通常叫TI1/2/3/4,我们称被测量的信号为Tix。

②输入滤波和边沿检测 当输入的信号存在高频干扰的时候,我们需要对输入信号进行滤波,即进行重新采样,根据采样定律,采样的频率必须大于等于两倍的输入信号。比如输入的信号为1M,又存在高频的信号干扰,那么此时就很有必要进行滤波,我们可以设置采样频率为2M,这样可以在保证采样到有效信号的基础上把高于2M的高频干扰信号过滤掉。

    滤波器的配置由CR1寄存器的位CKD[1:O]和CCMR1/2的位ICxF[3:0]控制。从ICxF位的描述可知,采样频率fSAMPLE可以由fCK INT和fDTs分频后的时钟提供,其中是fCK_INT内部时钟,Ds是fCK INT经过分频后得到的频率,分频因子由CKD[1:O]决定,可以是不分频,2分频或者是4分频。

边沿检测器用来设置信号在捕获的时候是什么边沿有效,可以是上升沿,下降沿,或者是双边沿,具体的由CCER寄存器的位CCxP和CCxNP决定。

③捕获通道 捕获通道就是图中的IC1/2/3/4,每个捕获通道都有相对应的捕获寄存器CCR1/2/3/4,当发生捕获的时候,计数器CNT的值就会被锁存到捕获寄存器中。

这里我们要搞清楚输入通道和捕获通道的区别,输入通道是用来输入信号的,捕获通道是用来捕获输入信号的通道,一个输入通道的信号可以同时输入给两个捕获通道。比如输入通道TI1的信号经过滤波边沿检测器之后的TIIFP1和TIIFP2可以进入到捕获通道IC1和IC2,其实这就是我们后面要讲的PWM输入捕获,只有一路输入信号(TI1)却占用了两个捕获通道(IC1和IC2),一路测量周期,一路测量占空比。PWM信号输入只能通道1、2有这种功能。PWM信号可以分为TIxFP1和TIxFP2,分别和两个捕获寄存器联系起来,要选择一路作为触发从模式,哪一路触发从模式,哪一路就是测量周期;另外一路测量占空比。TIxFP1触发从模式,测量周期称为直连;TIxFP2触发从模式,测量周期称为非直连。

当只需要测量输入信号的脉宽时候,用一个捕获通道即可。输入通道和捕获通道的映射关系具体由寄存器CCMRx的位CCxS[1:0]配置。

④预分频器 一般不分频。

⑤捕获寄存器 经过预分频器的信号ICxPS是最终被捕获的信号,当发生捕获时(第一次),计数器CNT的值会被锁存到捕获寄存器CCR中,还会产生CCxI中断,相应的中断位CCxIF(在SR寄存器中)会被置位,通过软件或者读取CCR中的值可以将CCxF清0。如果发生第二次捕获(即重复捕获:CCR寄存器中已捕获到计数器值且CCxF标志已置1),则捕获溢出标志位CCxOF(在SR寄存器中)会被置位,CCxOF只能通过软件清零。

(4) 输出比较

例如:输出一个PWM波信号。

CCR寄存器用于输出信号叫做比较寄存器。计数器从0开始计数,计数到CCR的时候输出高电平或者低电平;从CCR计数到ARR输出低电平或者高电平。然后重新开始计数一轮。占空比就与比较寄存器CCR相关。信号在输出通道输出之前,还要经过一个寄存器的控制:刹车和死区寄存器(TIMx_BDTR)。

什么是死区时间?

MOTORA是用来驱动电机的,由上下两个MOS管来控制,

一路(一个MOS管)高电平转换成低电平之后,延迟一段时间之后另一路(另一个MOS管)才由低电平转换成高电平;反之亦然。这样就不会同时导通,从而避免功率元件烧毁;延迟的那段时间称为死区时间,死区时间控制在通常的单片机所配备的PWM中都有这样的功能。

死区时间由刹车和死区寄存器(TIMx_BDTR)的UTG[7:0]位控制。

其中的Tdts跟内部时钟相关,由TIM1和TIM8控制寄存器 1(TIMx_CR1)的CKD[1:0]位控制。

在刹车和死区寄存器(TIMx_BDTR)中:

二、输入捕获的应用

一是测量脉宽和频率

上升沿捕获到一次之后进入中断,在中断中改变捕获的边沿,改成下降沿捕获;捕获到下降沿之后,进入中断再次改变为上升沿捕获……

二是PWM输入模式

只能通过通道1,2进行捕获,3、4通道不能捕获。

三、输出比较的应用

1-输出比较模式总共有 8 种,常用的是PWM模式。

2-由寄存器CCMRx的位OCxM[2:0]配置。

PWM 输出就是对外输出脉宽(即占空比)可调的方波信号,信号频率由自动重装寄存器 ARR 的值决定,占空比由比较寄存器 CCR 的值决定。

PWM的输出模式:

有效:高电平;无效:低电平

边沿对齐与中心对齐的不同

最常用的是边沿对齐

1-根据CNT的计数方向,PWM波形分成边沿对齐和中心对齐两种。边沿对齐主要用于直流电机,中心对齐主要用于交流电机。

2-边沿对齐时,CNT只工作在递增或者递减。

3-中心对齐时,CNT工作在递增和递减。

四、初始化结构体

(1) 时基初始化结构体

TIM_TimeBaseInitTypeDef,也就是前面功能框图里面的时基单元。

typedef struct 
{
    uint16_t TIM_Prescaler;         // 预分频器
    uint16_t TIM_CounterMode;       // 计数模式
    uint32_t TIM_Period;            // 定时器周期
    uint16_t TIM_ClockDivision;     // 时钟分频
    uint8_t TIM_RepetitionCounter;  // 重复计算器
} TIM_TimeBaseInitTypeDef;

TIM_Prescaler:定时器预分频器设置,时钟源经该预分频器才是定时器计数时钟CK_CNT,它设定 PSC 寄存器的值。计算公式为: 计数器时钟频率 (fCK_CNT) 等于fCK_PSC / (PSC[15:0] + 1)【对应TIMx_PSC寄存器】,可实现 1 至 65536 分频。

TIM_CounterMode:定时器计数方式,可设置为向上计数、向下计数以及中心对齐。高级控制定时器允许选择任意一种。基本定时器只能选择向上计数,配置基本定时器时不用配置此成员。对应TIM_CR1寄存器的DIR位。

TIM_Period:定时器周期,实际就是设定自动重载寄存器ARR的值,ARR为要装载到实际自动重载寄存器(即影子寄存器) 的值, 可设置范围为0至65535。(具体介绍看基本定时器)

TIM_ClockDivision:时钟分频,设置定时器时钟 CK_INT 频率与死区发生器以及数字滤波器采样时钟频率分频比。可以选择1、2、4 分频。

TIM_RepetitionCounter:重复计数器,只有 8 位,只存在于高级定时器。

(2) 输出比较结构体

TIM_OCInitTypeDef

typedef struct 
{
    uint16_t TIM_OCMode;            // 比较输出模式
    uint16_t TIM_OutputState;       // 比较输出使能
    uint16_t TIM_OutputNState;      // 比较互补输出使能
    uint32_t TIM_Pulse;             // 脉冲宽度
    uint16_t TIM_OCPolarity;        // 输出极性
    uint16_t TIM_OCNPolarity;       // 互补输出极性
    uint16_t TIM_OCIdleState;       // 空闲状态下比较输出状态
    uint16_t TIM_OCNIdleState;      // 空闲状态下比较互补输出状态
} TIM_OCInitTypeDef;

TIM_OCMode:比较输出模式选择,总共有八种,常用的为 PWM1/PWM2。它设定CCMRx(x控制1或2通道)寄存器OCxM[2:0]位的值。

TIM_OutputState:比较输出使能,决定最终的输出比较信号OCx 是否通过外部引脚输出。它设定TIMx_CCER寄存器CCxE位的值。也就是说启用之后可以产生PWM波信号。

TIM_OutputNState:比较互补输出使能,决定 OCx 的互补信号 OCxN 是否通过外部引脚输出。它设定 CCER 寄存器 CCxNE 位的值。

TIM_Pulse:比较输出脉冲宽度,实际设定比较寄存器 CCR 的值,决定脉冲宽度。可设置范围为 0 至 65535。

TIM_OCPolarity:比较输出极性,可选 OCx 为高电平有效或低电平有效。它决定着定时器通道有效电平。它设定 CCER 寄存器的 CCxP 位的值。也就是正周期的电平是高/低。

TIM_OCNPolarity:比较互补输出极性,可选OCxN 为高电平有效或低电平有效。它设定 TIMx_CCER 寄存器的 CCxNP 位的值。

当我们需要输出PWM波时(小车电机驱动),就需要配置时基结构体和输出比较结构体即可,而输出比较结构体只需要配置三个加粗的成员即可。

(3) PWM信号 周期和占空比的计算–以通用定时器为例

这一部分在时基结构体初始化中配置

ARR :自动重装载寄存器的值

CLK_cnt:计数器的时钟(驱动CNT计数器的时钟周期),等于 Fck_int / (psc+1) = 72M/(psc+1)  psc = GENERAL_TIM_Prescaler 宏定义的值为 71。CLK_cnt = 1 MHZ

PWM 信号的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M。这里的ARR代表的是自动重装载寄存器的值,累计TIM_Period+1,TIM_Period在宏定义中为9,也就是9+1=10。周期 = 10 * (1 / 1 MHZ) = 10 us

占空比 P = CCR/ARR = CCR/(TIM_Period+1)  TIM_Period就是宏定义中 GENERAL_TIM_Period 值为9,也就是说,占空比为 = CCR / 10

详细例程如下,这是一个定时器3,四路PWM通道不同输出值的配置程序,主要是看一下PWM周期和占空比是如何配置的:

.c文件内容,定时器3配置函数:

static void GENERAL_TIM3_Mode_Config(void)
{
  // 开启定时器时钟,即内部时钟CK_INT=72M
	GENERAL_TIM3_APBxClock_FUN(GENERAL_TIM3_CLK,ENABLE);

/*--------------------时基结构体初始化-------------------------*/
	// 配置周期,这里配置为100K
	
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	// 自动重装载寄存器的值ARR,累计TIM_Period+1个频率后产生一个更新或者中断  	TIM_Period+1  9+1=10
	TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM3_Period;	
	// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
	TIM_TimeBaseStructure.TIM_Prescaler= GENERAL_TIM3_Prescaler;	
	// 时钟分频因子 ,配置死区时间时需要用到
	TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;		
	// 计数器计数模式,设置为向上计数
	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;		
	// 重复计数器的值,没用到不用管
	TIM_TimeBaseStructure.TIM_RepetitionCounter=0;	
	// 初始化定时器
	TIM_TimeBaseInit(GENERAL_TIM3, &TIM_TimeBaseStructure);

	/*--------------------输出比较结构体初始化-------------------*/	 // 需要 PWM波 输出的就需要配置此结构体
	// 占空比配置
	uint16_t CCR1_Val = 9;  // 占空比 = CCR1_Val / (GENERAL_TIM_Period + 1)
	uint16_t CCR2_Val = 6;
	uint16_t CCR3_Val = 3;
	uint16_t CCR4_Val = 1;
	
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	// 配置为PWM模式1
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;  //比较输出模式选择.常用的为 PWM1/PWM2
	// 输出使能
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  //比较输出使能,决定最终的输出比较信号OCx是否通过外部引脚输出,也就是说启用之后可以产生PWM波信号
	// 输出通道电平极性配置	
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;  //比较输出极性,可选 OCx 为高电平有效或低电平有效。它决定着定时器通道有效电平。也就是正周期的电平是高/低
	
	// 输出比较通道 1
	TIM_OCInitStructure.TIM_Pulse = CCR1_Val;
	TIM_OC1Init(GENERAL_TIM3, &TIM_OCInitStructure);
	TIM_OC1PreloadConfig(GENERAL_TIM3, TIM_OCPreload_Enable);
	
	// 输出比较通道 2
	TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
	TIM_OC2Init(GENERAL_TIM3, &TIM_OCInitStructure);
	TIM_OC2PreloadConfig(GENERAL_TIM3, TIM_OCPreload_Enable);
	
	// 输出比较通道 3
	TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
	TIM_OC3Init(GENERAL_TIM3, &TIM_OCInitStructure);
	TIM_OC3PreloadConfig(GENERAL_TIM3, TIM_OCPreload_Enable);
	
	// 输出比较通道 4
	TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
	TIM_OC4Init(GENERAL_TIM3, &TIM_OCInitStructure);
	TIM_OC4PreloadConfig(GENERAL_TIM3, TIM_OCPreload_Enable);
	
	// 使能计数器
	TIM_Cmd(GENERAL_TIM3, ENABLE);
}

.h文件内容,一些宏定义:

/************************************通用定时器TIM参数定义,只限TIM2、3、4、5********************************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
/******************************* TIM3 ***************************************/

#define            GENERAL_TIM3                   TIM3
#define            GENERAL_TIM3_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            GENERAL_TIM3_CLK               RCC_APB1Periph_TIM3
#define            GENERAL_TIM3_Period            9
#define            GENERAL_TIM3_Prescaler         71
// TIM3 输出比较通道1
#define            GENERAL_TIM3_CH1_GPIO_CLK      RCC_APB2Periph_GPIOA
#define            GENERAL_TIM3_CH1_PORT          GPIOA
#define            GENERAL_TIM3_CH1_PIN           GPIO_Pin_6

// TIM3 输出比较通道2
#define            GENERAL_TIM3_CH2_GPIO_CLK      RCC_APB2Periph_GPIOA
#define            GENERAL_TIM3_CH2_PORT          GPIOA
#define            GENERAL_TIM3_CH2_PIN           GPIO_Pin_7

// TIM3 输出比较通道3
#define            GENERAL_TIM3_CH3_GPIO_CLK      RCC_APB2Periph_GPIOB
#define            GENERAL_TIM3_CH3_PORT          GPIOB
#define            GENERAL_TIM3_CH3_PIN           GPIO_Pin_0

// TIM3 输出比较通道4
#define            GENERAL_TIM3_CH4_GPIO_CLK      RCC_APB2Periph_GPIOB
#define            GENERAL_TIM3_CH4_PORT          GPIOB
#define            GENERAL_TIM3_CH4_PIN           GPIO_Pin_1

整个工程的详细代码在文末有下载链接。参照  5.2-通用定时器PWM输出功能设计

(4) 输入捕获结构体

typedef struct 
{
    uint16_t TIM_Channel; // 输入通道选择
    uint16_t TIM_ICPolarity; // 输入捕获触发选择
    uint16_t TIM_ICSelection; // 输入捕获选择
    uint16_t TIM_ICPrescaler; // 输入捕获预分频器
    uint16_t TIM_ICFilter; // 输入捕获滤波器
} TIM_ICInitTypeDef;

TIM_Channel:捕获通道ICx选择,可选 TIM_Channel_1、 TIM_Channel_2、TIM_Channel_3 或 TIM_Channel_4 四个通道。它设定 CCMRx 寄存器 CCxS 位 的值。

TIM_ICPolarity:输入捕获边沿触发选择,可选上升沿触发、下降沿触发或边沿跳变触发。它设定 CCER 寄存器 CCxP 位和 CCxNP 位的值。

TIM_ICSelection:输入通道选择,捕获通道 ICx 的信号可来自三个输入通道,分别为:TIM_ICSelection_DirectTI、TIM_ICSelection_IndirectTI 或 TIM_ICSelection_TRC。它设定 CCRMx 寄存器的 CCxS[1:0]位的值。

TIM_ICPrescaler:输入捕获通道预分频器,可设置 1、 2、 4、 8 分频,它设定 CCMRx寄存器的 ICxPSC[1:0]位的值。如果需要捕获输入信号的每个有效边沿,则设置 1 分频即可。

TIM_ICFilter:输入捕获滤波器设置,可选设置 0x0 至 0x0F。它设定 CCMRx 寄存器ICxF[3:0]位的值。一般我们不使用滤波器,即设置为 0。

(5) 断路和死区初始化结构体

 TIM_BDTRInitTypeDef

断路和死区结构体 TIM_BDTRInitTypeDef 用于断路和死区参数的设置,属于高级定时器专用,用 于配置断路时通道输出状态,以及死区时间。它与 TIM_BDTRConfig 函数配置使用完成参数配置。 这个结构体的成员只对应寄存器断路和死区寄存器:TIMx_BDTR。 

typedef struct 
{
    uint16_t TIM_OSSRState;         // 运行模式下的关闭状态选择
    uint16_t TIM_OSSIState;         // 空闲模式下的关闭状态选择
    uint16_t TIM_LOCKLevel;         // 锁定配置
    uint16_t TIM_DeadTime;          // 死区时间
    uint16_t TIM_Break;             // 断路输入使能控制
    uint16_t TIM_BreakPolarity;     // 断路输入极性
    uint16_t TIM_AutomaticOutput;   // 自动输出使能
} TIM_BDTRInitTypeDef;

 (1) TIM_OSSRState:运行模式下的关闭状态选择,它设定 BDTR 寄存器 OSSR 位的值。

 (2) TIM_OSSIState:空闲模式下的关闭状态选择,它设定 BDTR 寄存器 OSSI 位的值。

 (3) TIM_LOCKLevel:锁定级别配置,BDTR 寄存器 LOCK[1:0] 位的值。

 (4) TIM_DeadTime:配置死区发生器,定义死区持续时间,可选设置范围为 0x0 至 0xFF。它设定 BDTR 寄存器 DTG[7:0] 位的值。

 (5) TIM_Break:断路输入功能选择,可选使能或禁止。它设定 BDTR 寄存器 BKE 位的值。

 (6) TIM_BreakPolarity:断路输入通道 BRK 极性选择,可选高电平有效或低电平有效。它设定 BDTR 寄存器 BKP 位的值。

 (7) TIM_AutomaticOutput:自动输出使能,可选使能或禁止,它设定 BDTR 寄存器 AOE 位的值。

这个在我平常做的时候用不到这个功能,就不做相应配置了。

五、程序设计

(1) 高级定时器定时功能设计

功能设计:使用高级定时器实现双色LED灯间隔1秒完成红绿颜色切换(高级定时器的定时功能)

配置过程:

首先进行定时器和中断优先级的配置:

#include "bsp_AdvanceTim.h" 

// 中断优先级配置
static void ADVANCE_TIM_NVIC_Config(void)
{
    NVIC_InitTypeDef NVIC_InitStructure; 
    // 设置中断组为0
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);		
		// 设置中断来源
    NVIC_InitStructure.NVIC_IRQChannel = ADVANCE_TIM_IRQ ;	
		// 设置主优先级为 0
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;	 
	  // 设置抢占优先级为3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;	
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

///*
// * 注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有
// * TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,
// * 另外三个成员是通用定时器和高级定时器才有.
// *-----------------------------------------------------------------------------
// *typedef struct
// *{ TIM_Prescaler            都有
// *	TIM_CounterMode			     TIMx,x[6,7]没有,其他都有
// *  TIM_Period               都有
// *  TIM_ClockDivision        TIMx,x[6,7]没有,其他都有
// *  TIM_RepetitionCounter    TIMx,x[1,8,15,16,17]才有
// *}TIM_TimeBaseInitTypeDef; 
// *-----------------------------------------------------------------------------
// */
static void ADVANCE_TIM_Mode_Config(void)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;		
		// 开启定时器时钟,即内部时钟CK_INT=72M
    ADVANCE_TIM_APBxClock_FUN(ADVANCE_TIM_CLK, ENABLE);	
		// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
    TIM_TimeBaseStructure.TIM_Period=ADVANCE_TIM_Period;
	  // 时钟预分频数
    TIM_TimeBaseStructure.TIM_Prescaler= ADVANCE_TIM_Prescaler;	
		// 时钟分频因子 ,没用到不用管
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;		
		// 计数器计数模式,设置为向上计数
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; 		
		// 重复计数器的值,没用到不用管
		TIM_TimeBaseStructure.TIM_RepetitionCounter=0;	
	  // 初始化定时器
    TIM_TimeBaseInit(ADVANCE_TIM, &TIM_TimeBaseStructure);
	
		// 清除计数器中断标志位
    TIM_ClearFlag(ADVANCE_TIM, TIM_FLAG_Update);
	  
		// 开启计数器中断
    TIM_ITConfig(ADVANCE_TIM,TIM_IT_Update,ENABLE);
		
		// 使能计数器
    TIM_Cmd(ADVANCE_TIM, ENABLE);
}

void ADVANCE_TIM_Init(void)
{
	ADVANCE_TIM_NVIC_Config();
	ADVANCE_TIM_Mode_Config();		
}

关于高级定时器的一些宏定义:

#ifndef __BSP_ADVANCETIME_H
#define __BSP_ADVANCETIME_H


#include "stm32f10x.h"


/********************高级定时器TIM参数定义,只限TIM1、8************/
#define ADVANCE_TIM1 // 如果使用TIM8,注释掉这个宏即可

#ifdef  ADVANCE_TIM1 // 使用高级定时器TIM1

#define            ADVANCE_TIM                   TIM1
#define            ADVANCE_TIM_APBxClock_FUN     RCC_APB2PeriphClockCmd
#define            ADVANCE_TIM_CLK               RCC_APB2Periph_TIM1
#define            ADVANCE_TIM_Period            (1000-1)
#define            ADVANCE_TIM_Prescaler         71
#define            ADVANCE_TIM_IRQ               TIM1_UP_IRQn
#define            ADVANCE_TIM_IRQHandler        TIM1_UP_IRQHandler

#else  // 使用高级定时器TIM8
#define            ADVANCE_TIM                   TIM8
#define            ADVANCE_TIM_APBxClock_FUN     RCC_APB2PeriphClockCmd
#define            ADVANCE_TIM_CLK               RCC_APB2Periph_TIM8
#define            ADVANCE_TIM_Period            (1000-1)
#define            ADVANCE_TIM_Prescaler         71
#define            ADVANCE_TIM_IRQ               TIM8_UP_IRQn
#define            ADVANCE_TIM_IRQHandler        TIM8_UP_IRQHandler

#endif
/**************************函数声明********************************/

void ADVANCE_TIM_Init(void);


#endif	/* __BSP_ADVANCETIME_H */


高级定时器的中断函数:

void  ADVANCE_TIM_IRQHandler (void)
{
	if ( TIM_GetITStatus( ADVANCE_TIM, TIM_IT_Update) != RESET ) 
	{	
		time++;
		TIM_ClearITPendingBit(ADVANCE_TIM , TIM_FLAG_Update);  		 
	}		 	
}

主函数程序:

int main(void)
{
	LED_GPIO_Config();//初始化函数
	ADVANCE_TIM_Init();
	
  while(1)
	{
		if(time == 1000)
		{	    
			GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
			GPIO_ResetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
		}
		else if(time == 2000)
		{
			time=0;
			GPIO_ResetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
			GPIO_SetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
		}
	}
}

想要验证这个程序,需要一个双色LED灯,当然也可以用别的外设去替代。完整程序链接在文章末尾:高级定时器定时功能设计源码。

(2) 通用定时器PWM输出功能设计

功能设计:通过四个通用定时器2、3、4、5分别实现LED灯四级亮度等级变化(4个通道有不同的占空比,代表4个不同的亮度等级)。这个程序只是展示了通用定时器的 PWM波输出功能,在使用此功能时,是不需要使用中断的。

配置过程:

单以定时器3为例,其实定时器3做出来了,其他的也就会了,只不过改改引脚即可。

首先配置定时器3:

#include "bsp_GeneralTim.h" 

/******************************** TIM3 PWM输出配置 ******************************/

static void GENERAL_TIM3_GPIO_Config(void) 
{
  GPIO_InitTypeDef GPIO_InitStructure;

  // 输出比较通道1 GPIO 初始化
	RCC_APB2PeriphClockCmd(GENERAL_TIM3_CH1_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM3_CH1_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM3_CH1_PORT, &GPIO_InitStructure);
	
	// 输出比较通道2 GPIO 初始化
	RCC_APB2PeriphClockCmd(GENERAL_TIM3_CH2_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM3_CH2_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM3_CH2_PORT, &GPIO_InitStructure);
	
	// 输出比较通道3 GPIO 初始化
	RCC_APB2PeriphClockCmd(GENERAL_TIM3_CH3_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM3_CH3_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM3_CH3_PORT, &GPIO_InitStructure);
	
	// 输出比较通道4 GPIO 初始化
	RCC_APB2PeriphClockCmd(GENERAL_TIM3_CH4_GPIO_CLK, ENABLE);
  GPIO_InitStructure.GPIO_Pin =  GENERAL_TIM3_CH4_PIN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GENERAL_TIM3_CH3_PORT, &GPIO_InitStructure);
}


///*
// * 注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有
// * TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,
// * 另外三个成员是通用定时器和高级定时器才有.
// *-----------------------------------------------------------------------------
// *typedef struct
// *{ TIM_Prescaler            都有   定时器的预分频值。预分频值越大,计数频率越低,定时器的计数周期就越长。
// *	TIM_CounterMode			     TIMx,x[6,7]没有,其他都有   定时器的计数模式(向上、向下、中心对齐)
// *  TIM_Period               都有   定时器的自动重装载值。当计数器达到自动重装载值时,会产生更新事件,并重新从0开始计数。
// *  TIM_ClockDivision        TIMx,x[6,7]没有,其他都有   定时器的时钟分频系数。用于将计数器时钟进一步分频,从而得到一个更低的计数频率。时钟分频系数的取值范围为1到65535。
// *  TIM_RepetitionCounter    TIMx,x[1,8,15,16,17]才有  配置定时器在重复计数模式下的重复计数次数。高级定时器特有
// *}TIM_TimeBaseInitTypeDef; 
// *-----------------------------------------------------------------------------
// */

/* ----------------   PWM信号 周期和占空比的计算--------------- */
// ARR :自动重装载寄存器的值
// CLK_cnt:计数器的时钟(驱动CNT计数器的时钟周期),等于 Fck_int / (psc+1) = 72M/(psc+1)  psc = GENERAL_TIM_Prescaler 宏定义的值为 71
//          CLK_cnt = 1 MHZ
// PWM 信号的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M  
//          这里的ARR代表的是自动重装载寄存器的值,累计TIM_Period+1,也就是9+1=10,跟下面的ARR不一样,下面的ARR没加一
//          周期 = 10 * (1 / 1 MHZ) = 10 us
// 占空比 P = CCR/(GENERAL_TIM_Period+1)  GENERAL_TIM_Period 宏定义中为9,也就是说,占空比为 = CCR / 10

static void GENERAL_TIM3_Mode_Config(void)
{
  // 开启定时器时钟,即内部时钟CK_INT=72M
	GENERAL_TIM3_APBxClock_FUN(GENERAL_TIM3_CLK,ENABLE);

/*--------------------时基结构体初始化-------------------------*/
	// 配置周期,这里配置为100K
	
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	// 自动重装载寄存器的值ARR,累计TIM_Period+1个频率后产生一个更新或者中断  	TIM_Period+1  9+1=10
	TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM3_Period;	
	// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
	TIM_TimeBaseStructure.TIM_Prescaler= GENERAL_TIM3_Prescaler;	
	// 时钟分频因子 ,配置死区时间时需要用到
	TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;		
	// 计数器计数模式,设置为向上计数
	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;		
	// 重复计数器的值,没用到不用管
	TIM_TimeBaseStructure.TIM_RepetitionCounter=0;	
	// 初始化定时器
	TIM_TimeBaseInit(GENERAL_TIM3, &TIM_TimeBaseStructure);

	/*--------------------输出比较结构体初始化-------------------*/	 // 需要 PWM波 输出的就需要配置此结构体
	// 占空比配置
	uint16_t CCR1_Val = 9;  // 占空比 = CCR1_Val / (GENERAL_TIM_Period + 1)
	uint16_t CCR2_Val = 6;
	uint16_t CCR3_Val = 3;
	uint16_t CCR4_Val = 1;
	
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	// 配置为PWM模式1
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;  //比较输出模式选择.常用的为 PWM1/PWM2
	// 输出使能
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  //比较输出使能,决定最终的输出比较信号OCx是否通过外部引脚输出,也就是说启用之后可以产生PWM波信号
	// 输出通道电平极性配置	
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;  //比较输出极性,可选 OCx 为高电平有效或低电平有效。它决定着定时器通道有效电平。也就是正周期的电平是高/低
	
	// 输出比较通道 1
	TIM_OCInitStructure.TIM_Pulse = CCR1_Val;
	TIM_OC1Init(GENERAL_TIM3, &TIM_OCInitStructure);
	TIM_OC1PreloadConfig(GENERAL_TIM3, TIM_OCPreload_Enable);
	
	// 输出比较通道 2
	TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
	TIM_OC2Init(GENERAL_TIM3, &TIM_OCInitStructure);
	TIM_OC2PreloadConfig(GENERAL_TIM3, TIM_OCPreload_Enable);
	
	// 输出比较通道 3
	TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
	TIM_OC3Init(GENERAL_TIM3, &TIM_OCInitStructure);
	TIM_OC3PreloadConfig(GENERAL_TIM3, TIM_OCPreload_Enable);
	
	// 输出比较通道 4
	TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
	TIM_OC4Init(GENERAL_TIM3, &TIM_OCInitStructure);
	TIM_OC4PreloadConfig(GENERAL_TIM3, TIM_OCPreload_Enable);
	
	// 使能计数器
	TIM_Cmd(GENERAL_TIM3, ENABLE);
}

void GENERAL_TIM3_Init(void)
{
	GENERAL_TIM3_GPIO_Config();  // 通用定时器GPIO口配置
	GENERAL_TIM3_Mode_Config();	// 通用定时器输出模式等配置	
}

.h文件进行引脚等宏定义:

#ifndef __BSP_GENERALTIME_H
#define __BSP_GENERALTIME_H


#include "stm32f10x.h"


/*******************通用定时器TIM参数定义,只限TIM2、3、4、5******************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
/******************************* TIM3 ***************************************/

#define            GENERAL_TIM3                   TIM3
#define            GENERAL_TIM3_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            GENERAL_TIM3_CLK               RCC_APB1Periph_TIM3
#define            GENERAL_TIM3_Period            9
#define            GENERAL_TIM3_Prescaler         71
// TIM3 输出比较通道1
#define            GENERAL_TIM3_CH1_GPIO_CLK      RCC_APB2Periph_GPIOA
#define            GENERAL_TIM3_CH1_PORT          GPIOA
#define            GENERAL_TIM3_CH1_PIN           GPIO_Pin_6

// TIM3 输出比较通道2
#define            GENERAL_TIM3_CH2_GPIO_CLK      RCC_APB2Periph_GPIOA
#define            GENERAL_TIM3_CH2_PORT          GPIOA
#define            GENERAL_TIM3_CH2_PIN           GPIO_Pin_7

// TIM3 输出比较通道3
#define            GENERAL_TIM3_CH3_GPIO_CLK      RCC_APB2Periph_GPIOB
#define            GENERAL_TIM3_CH3_PORT          GPIOB
#define            GENERAL_TIM3_CH3_PIN           GPIO_Pin_0

// TIM3 输出比较通道4
#define            GENERAL_TIM3_CH4_GPIO_CLK      RCC_APB2Periph_GPIOB
#define            GENERAL_TIM3_CH4_PORT          GPIOB
#define            GENERAL_TIM3_CH4_PIN           GPIO_Pin_1

/**************************函数声明********************************/

void GENERAL_TIM3_Init(void);

#endif	/* __BSP_GENERALTIME_H */

main函数:

int main(void)
{
	LED_GPIO_Config();//初始化函数
	//GENERAL_TIM3_Init();
	//GENERAL_TIM2_Init();
	//GENERAL_TIM4_Init();
	GENERAL_TIM5_Init();
	
  while(1)
	{
		GPIO_ResetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
	}
}

想要验证这个程序,需要一个LED灯,当然也可以用别的可现实PWM变化的外设去替代。下载链接见文章末尾:通用定时器PWM输出功能设计源码。

文章案例程序源码下载

一、SysTick定时器的延时函数(毫秒、微秒)源码:

链接:https://pan.baidu.com/s/1NPIMtPrXriXqgUAxwrOPVw 
提取码:rf8k 
–来自百度网盘超级会员V1的分享

二、基本定时器LED反转设计源码:

链接:https://pan.baidu.com/s/13eb3qHm4YVtHELPXYBJXdQ 
提取码:hjaw 

三、高级定时器定时功能设计源码:

链接:https://pan.baidu.com/s/1ZVEE6FaTZV8grSiQIiQrWg 
提取码:ssun 
–来自百度网盘超级会员V1的分享

四、通用定时器PWM输出功能设计源码:

链接:https://pan.baidu.com/s/1BLrr-MkQEAeBW7fP4Z7O4g 
提取码:wa4e 
–来自百度网盘超级会员V1的分享

参考博客:

STM32 定时器介绍–基本定时器-CSDN博客

STM32 系统定时器–SysTick_systick中断-CSDN博客

STM32 定时器介绍–通用、高级定时器_高级定时器和通用定时器_Haohao fighting!的博客-CSDN博客

物联沃分享整理
物联沃-IOTWORD物联网 » 深入了解STM32定时器的功能与应用

发表评论