30.定时器中断原理与配置

定时器中断原理与配置

正点原子STM32FX开发板:
《STM32FX开发指南-HAL库版本》- 定时器相关实验
STM32Fxx官方资料:
《STM32Fxx中文参考手册》-第x章 通用定时器

笔记基于正点原子官方视频
视频连接https://www.bilibili.com/video/BV1Wx411d7wT?p=71&spm_id_from=333.1007.top_right_bar_window_history.content.click
如有侵权,联系删除

一、定时器知识准备

1.通用定时器工作过程

30.定时器中断原理与配置_第1张图片

2.时钟选择

1、计数器时钟可以由下列时钟源提供
① 内部时钟(CK_INT)
② 外部时钟模式1:外部输入脚(TIx)
③ 外部时钟模式2:外部触发输入(ETR)(仅适用TIM2,3,4)
④ 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。

2、内部时钟选择
30.定时器中断原理与配置_第2张图片
以F429为例,默认调用时钟初始化函数之后:
SYSCLK(系统时钟) =180MHz
PLL 主时钟 =180MHz
AHB 总线时钟(HCLK=SYSCLK/1) =180MHz
APB1 总线时钟(PCLK1=HCLK/4) =45MHz
APB2 总线时钟(PCLK2=HCLK/2) =90MHz
所以APB1的分频系数=AHB/APB1时钟=4
所以,通用定时器时钟CK_INT=2*42M=84M

3.计数器模式

通用定时器可以向上计数、向下计数、向上向下双向计数模式。
①向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
②向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
③中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
30.定时器中断原理与配置_第3张图片

1)向下计数模式(时钟分频因子=1)

30.定时器中断原理与配置_第4张图片

2)向上计数模式(时钟分频因子=1)

30.定时器中断原理与配置_第5张图片

3)中央对齐计数模式(时钟分频因子=1 ARR=6)

30.定时器中断原理与配置_第6张图片

二、常用寄存器和库函数配置

1.定时器中断实验相关寄存器
1)计数器当前值寄存器CNT

30.定时器中断原理与配置_第7张图片

在这个寄存器里面进行数据的向上计数和向下计数。

2)预分频寄存器TIMx_PSC

30.定时器中断原理与配置_第8张图片

3)自动重装载寄存器(TIMx_ARR)

30.定时器中断原理与配置_第9张图片

4)控制寄存器1(TIMx_CR1)

30.定时器中断原理与配置_第10张图片

5)DMA中断使能寄存器(TIMx_DIER)

30.定时器中断原理与配置_第11张图片

2.定时器中断实验相关函数
1)通用定时器基本函数和定义所在文件

Stm32fxxx_hal_tim.c
位置:工程文件 - HALLIB - Stm32fxxx_hal_tim.c
Stm32fxxx_hal_tim.h
位置:工程文件 - HALLIB - Stm32fxxx_hal_tim.c - Stm32fxxx_hal_tim.h

在Stm32fxxx_hal_tim.h中有三部分函数体,如下:
时基部分
30.定时器中断原理与配置_第12张图片

输出比较
30.定时器中断原理与配置_第13张图片

PWM部分
30.定时器中断原理与配置_第14张图片

输入捕获
30.定时器中断原理与配置_第15张图片

2)定时器时基参数初始化函数

函数体:HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim);
位置:工程文件 - HALLIB - Stm32fxxx_hal_tim.c
30.定时器中断原理与配置_第16张图片

typedef struct
{
	uint32_t Prescaler; 					//预分频系数
	uint32_t CounterMode; 			//计数模式:向上/下
	uint32_t Period;						 //自动装载值
	uint32_t ClockDivision; 			//时钟分频因子:定时器时钟与数字滤波器分频比
	uint32_t RepetitionCounter; 	//重复计数次数:高级定时器使用
} TIM_Base_InitTypeDef
3)定时器时基参数初始化回调函数

函数体:void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim);
位置:工程文件 - HALLIB - Stm32fxxx_hal_tim.c
30.定时器中断原理与配置_第17张图片

主要用来编写定时器时钟使能,以及中断优先级。

__HAL_RCC_TIM3_CLK_ENABLE();		//定时器3时钟使能
4)使能定时器

函数体:HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim)
函数体:HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
位置:工程文件 - HALLIB - Stm32fxxx_hal_tim.c - Stm32fxxx_hal_tim.h
在这里插入图片描述

此函数同时开启了定时器更新中断

5)定时器中断通用处理函数

函数体:void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim);
定义所在位置:工程文件 - HALLIB - Stm32fxxx_hal_tim.c
调用位置:工程文件 - HARDWARE - timer.c

该函数被中断服务函数调用。是定时器中断处理通用入口函数,通过对中断类型进行分析判断,调用对应的回调函数

6)定时器中断处理回调函数:

函数体:void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
函数体:void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
函数体:void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
函数体:void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
函数体:void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim)
函数体:void HAL_TIM_ErrorCallback(TIM_HandleTypeDef *htim)

3.定时器中断实现步骤

① 使能定时器时钟。

 __HAL_RCC_TIM3_CLK_ENABLE();

② 初始化定时器,配置ARR,PSC。

HAL_TIM_Base_Init();

③ 开启定时器/中断。

HAL_TIM_Base_Start();
HAL_TIM_Base_Start_IT();

④ 设置中断优先级。

HAL_NVIC_SetPriority(); HAL_NVIC_EnableIRQ(); 

⑤ 编写中断服务函数。

TIMx_IRQHandler();//中断服务函数
HAL_TIM_IRQHandler();//中断处理入口函数
HAL_TIM_PeriodElapsedCallback();//定时器更新中断回调函数

三、定时器中断实验

要求:
通过定时器中断配置,每500ms中断一次,然后中断服务函数中控制LED1实现LED1状态取反(闪烁)。
Tout(溢出时间)=(ARR+1)(PSC+1)/Tclk
本实验以之前小节03.按键输入实验为基础做改进

**注意:**在F429中Tclk的值为90MHz,即90x10^6Hz。
如果要设置溢出时间为0.5s,则可以令ARR+1=5000,PSC+1=9000
即Tout(溢出时间)=(4999+1)(8999+1)/90x10^6=0.5s

1.实验前准备

30.定时器中断原理与配置_第18张图片
添加完,编译一下所有文件

2.初始化定时器,配置ARR,PSC。
1)调用:HAL_TIM_Base_Init();

新建一个TIM3_Init函数,在里面调用HAL_TIM_Base_Init(TIM_HandleTypeDef *htim);
因为HAL_TIM_Base_Init();里面调用的是一个结构体变量,所以我们要定义一个变量来指向该结构体变量

在主函数中写入以下程序:

TIM_HandleTypeDef TIM3_Handler;

void TIM3_Init(void)
{
	
	HAL_TIM_Base_Init(&TIM3_Handler);
}

30.定时器中断原理与配置_第19张图片

2)配置上面的TIM3_Handler

查看定义发现可定义变量如下:
30.定时器中断原理与配置_第20张图片

配置Instance
30.定时器中断原理与配置_第21张图片
30.定时器中断原理与配置_第22张图片

如下:配置TIM3
30.定时器中断原理与配置_第23张图片

配置Init
因为Init为一个结构体变量,我们需要对其子变量进行配置,
30.定时器中断原理与配置_第24张图片
这里我们需要配置其中三个变量:
Prescaler 预分频系数(PSC)
CounterMode 计数模式
Period 自动装载值(ARR)

查看CounterMode定义如下,设置向上计数
30.定时器中断原理与配置_第25张图片
在这里插入图片描述

设置如下:

	TIM3_Handler.Init.Prescaler = 9000 - 1;
	TIM3_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
	TIM3_Handler.Init.Period = 5000 - 1;

即Tout(溢出时间)=(ARR+1)(PSC+1)/Tclk
Tout(溢出时间)=(5000)(9000)/90x10^6=0.5s

至此初始化定时器,配置ARR,PSC,配置完毕,代码如下:

TIM_HandleTypeDef TIM3_Handler;

void TIM3_Init(void)
{
	TIM3_Handler.Instance = TIM3;
	TIM3_Handler.Init.Prescaler = 9000 - 1;
	TIM3_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
	TIM3_Handler.Init.Period = 5000 - 1;
	HAL_TIM_Base_Init(&TIM3_Handler);
}

30.定时器中断原理与配置_第26张图片

3.使能定时器时钟、开启定时器/中断。

__HAL_RCC_TIM3_CLK_ENABLE();
HAL_TIM_Base_Start();
HAL_TIM_Base_Start_IT();

1)初始化时基函数

初始化时基函数,在里面调用使能定时器时钟函数,代码如下

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
	__HAL_RCC_TIM3_CLK_ENABLE();
}
3.设置中断优先级。

HAL_NVIC_SetPriority(); HAL_NVIC_EnableIRQ();

开启定时器/中断,把HAL_TIM_Base_Start_IT(&TIM3_Handler);函数放在初始化定时器函数体里面

设置中断优先级
复制 工程文件 - SYSTEM - usart.c中的两行代码到主函数
在这里插入图片描述

把使能变量改为TIM3_IRQn,设置抢占优先级为1,代码如下

HAL_NVIC_EnableIRQ(TIM3_IRQn);				//使能TIM3中断通道
HAL_NVIC_SetPriority(TIM3_IRQn,1,3);			//抢占优先级1,子优先级3

在这里插入图片描述

4.编写中断服务函数。

TIMx_IRQHandler();//中断服务函数
HAL_TIM_IRQHandler();//中断处理入口函数
HAL_TIM_PeriodElapsedCallback();//定时器更新中断回调函数

1)中断服务函数框架

在启动文件 位置:工程文件 - CORE - startup_stm32f429xx.s 中找到中断服务函数函数体
30.定时器中断原理与配置_第27张图片
编写中断服务函数框架如下
30.定时器中断原理与配置_第28张图片

2)定时器中断通用处理函数

void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim);
引入TIM3_Handler
HAL_TIM_IRQHandler(&TIM3_Handler); //定时器中断通用处理函数

代码如下:

void TIM3_IRQHandler(void)
{
	HAL_TIM_IRQHandler(&TIM3_Handler);		//定时器中断通用处理函数
}
5)重写定时器更新中断回调函数

__weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
因为是个弱函数所以需要对在main.c函数中重写

因为在工程文件 - HARDWARE - lde.c - led.h 中有对输出口(LED0、LED1)进行位操作,所以可以直接调用
代码如下

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)		//重写定时器更新中断回调函数
{
	if (htim->Instance == TIM3)		//判断是否是定时器3发生中断响应
	{
			LED1!=LED1 ;			//进行LED1的反转以实现亮灭
	}
}
6)在主函数中调用定时器中断函数

为了做对比,我在主函数while函数中写入LED0闪烁函数(采用延时方式),闪烁周期不同,以做对比。代码如下:

int main(void)
{
    HAL_Init();                     //初始化HAL库   
    Stm32_Clock_Init(360,25,2,8);   //设置时钟,180Mhz
    delay_init(180);                //初始化延时函数
    uart_init(115200);              //初始化USART
    LED_Init();                     //初始化LED 
    KEY_Init();                     //初始化按键
	
	TIM3_Init();					//调用定时器中断函数
	
    while(1)
    {
		LED0 =! LED0;			//延时法闪烁LED0,与LED1做对比
		delay_ms(200);
    }
}

至此所有函数编辑完毕,main.c代码如下:

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"

TIM_HandleTypeDef TIM3_Handler;

void TIM3_Init(void)
{
	TIM3_Handler.Instance = TIM3;							//选定定时器3
	TIM3_Handler.Init.Prescaler = 9000 - 1;					//预分频系数(PSC)
	TIM3_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;		//计数模式,向上计数
	TIM3_Handler.Init.Period = 5000 - 1;					//自动装载值(ARR)
	HAL_TIM_Base_Init(&TIM3_Handler);
	
	HAL_TIM_Base_Start_IT(&TIM3_Handler);					//使能定时器3,并开启更新中断
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
	__HAL_RCC_TIM3_CLK_ENABLE();			//初始化时基函数
	
	HAL_NVIC_EnableIRQ(TIM3_IRQn);				//使能TIM3中断通道
	HAL_NVIC_SetPriority(TIM3_IRQn,1,3);			//抢占优先级1,子优先级3
}

void TIM3_IRQHandler(void)
{
	HAL_TIM_IRQHandler(&TIM3_Handler);		//定时器中断通用处理函数 
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)		//重写定时器更新中断回调函数
{
	if (htim->Instance == TIM3)		//判断是否是定时器3发生中断响应
	{
			LED1 =! LED1 ;			//进行LED1的反转以实现亮灭
	}
}

int main(void)
{

    HAL_Init();                     //初始化HAL库   
    Stm32_Clock_Init(360,25,2,8);   //设置时钟,180Mhz
    delay_init(180);                //初始化延时函数
    uart_init(115200);              //初始化USART
    LED_Init();                     //初始化LED 
    KEY_Init();                     //初始化按键
	
	TIM3_Init();					//调用定时器中断函数
	
    while(1)
    {
		LED0 =! LED0;			//延时法闪烁LED0,与LED1做对比
		delay_ms(200);
    }
}

你可能感兴趣的:(单片机,stm32,嵌入式硬件)