十六、定时器灯闪烁
配置流程
一般使用定时器功能,都需要有以下几个步骤。
- 开启时钟(定时器时钟)
- 配置定时器参数
- 配置中断优先级
- 使能中断事件和定时器
- 编写中断服务函数
STM32F407VET6 一共有 14 个定时器,不同类型的定时器功能不同。这一章就以 LED 灯 1s 闪烁为实验内容进行讲解。实现定时 1 秒钟进入中断,只需要实现基本的定时功能,STM32 的 14 个定时器都可以使用,这里就以定时器 3 为例进行介绍。
开启时钟
先来看一下定时器的时钟来源,在数据手册的第 19 页,如图所示。
从图可以看到 14 个定时器的时钟来源主要分为两部分,第一部分来源于 APB1,第二部分来源于 APB2。APB1 最大 42MHz,APB2 最大 84MHz。
这里使用 TIMER3,就要先使能 TIMER3 的时钟,又因为 TIMER3 时钟来源于 APB1,APB1 的时钟在 system_stm32f4xx.c 中定义。
对于 APB1 时钟来说,APB1 是 AHB 总线的 4 分频率也就是 168/4=42,定时器时钟拥有一个 x2 的倍频会将当前总线 APB1 频率 x2,将 x2 后的时钟频率作为挂载在 APB1 总线上定时器的时钟频率。
所以 TIMER3 的时钟是 84MHz
开启时钟:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
配置定时器参数
开启时钟之后,需要配置一些定时器的参数,比如定时时间,计数模式等。
关于基本定时器的结构框图如图所示。
要初始化定时器就要先配置这个结构体,首先定义这个结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
然后配置结构体的参数
TIM_ClockDivision (TIM_CKD):
- 这个成员用于设置定时器的时钟分频,影响定时器内部用于输入捕获、输出比较等模块的数字滤波器的采样频率。常用的值有
TIM_CKD_DIV1
、TIM_CKD_DIV2
、TIM_CKD_DIV4
。
- 这个成员用于设置定时器的时钟分频,影响定时器内部用于输入捕获、输出比较等模块的数字滤波器的采样频率。常用的值有
TIM_CounterMode:
- 定义定时器的计数模式。常见的计数模式包括向上计数(
TIM_CounterMode_Up
)、向下计数(TIM_CounterMode_Down
)和中心对齐计数模式(TIM_CounterMode_CenterAligned1
、TIM_CounterMode_CenterAligned2
、TIM_CounterMode_CenterAligned3
)。
- 定义定时器的计数模式。常见的计数模式包括向上计数(
TIM_Period:
- 定时器的自动重装载寄存器的值。这个值决定了定时器溢出(或者说更新事件)的时间点。定时器的计数值达到这个值时,会产生一个更新事件(也可能是中断,如果使能的话),并且计数器值重置为 0(向上计数模式)或重置为自动重装载寄存器的值(向下计数模式)。
TIM_Prescaler:
- 定时器的预分频值。这个值用于减慢定时器计数器的计数速度。定时器的输入时钟(通常是 APB 总线时钟)会被这个预分频值加 1 后的值分频,然后才用于定时器的计数器。例如,如果预分频值设置为 0,则定时器计数器的时钟频率等于定时器的输入时钟频率;如果预分频值设置为 1,则计数器的时钟频率是输入时钟频率的一半。
配置好参数之后,就可以初始化定时器了。
关于定时器参数配置和初始化定时器代码如下:
/* 配置定时器参数 */
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = 10000;
TIM_TimeBaseStructure.TIM_Prescaler = 8400 - 1; // TIMER3时钟频率84MHz
2
3
4
5
配置中断优先级
定时器的参数配置好之后,定时器基本就配置好了。不过我们需要在中断函数中去执行对应的功能,就要对中断进行操作。前面介绍过,如果要使用中断功能,就需要配置中断优先级。中断分组还继续沿用之前的配置。
配置 TIMER3 中断的结构体:
NVIC_InitTypeDef NVIC_InitStructure;
2
配置 TIMER3 中断参数:
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;
2
3
4
NVIC_IRQChannel:
- 这个成员用于指定要配置的中断请求(IRQ)通道。
TIM3_IRQn
是定时器 3 的中断请求编号,表示这次配置是针对定时器 3 的中断。
- 这个成员用于指定要配置的中断请求(IRQ)通道。
NVIC_IRQChannelCmd:
- 该成员用于启用或禁用指定的中断通道。
ENABLE
表示启用定时器 3 的中断。
- 该成员用于启用或禁用指定的中断通道。
NVIC_IRQChannelPreemptionPriority:
- 这个成员设置中断的抢占优先级。在 STM32 中,较低的数值表示较高的优先级。
0x01
表示此中断的抢占优先级设置为 1。抢占优先级用于在多个中断同时请求时决定哪个中断先被执行。
- 这个成员设置中断的抢占优先级。在 STM32 中,较低的数值表示较高的优先级。
NVIC_IRQChannelSubPriority:
- 设置中断的子优先级。在同一抢占优先级内,子优先级决定了中断的优先顺序。
0x03
表示此中断的子优先级为 3。子优先级在同一抢占优先级的中断之间决定优先顺序。
- 设置中断的子优先级。在同一抢占优先级内,子优先级决定了中断的优先顺序。
使能中断事件和定时器
初始化定时器:
void TIM_TimeBaseInit((TlM TypeDef* TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
初始化中断:
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
NVIC_Init(&NVIC_InitStructure);
使能中断:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState)
TIM_ITConfig(TIM3,TIM_IT_Update, ENABLE);
使能定时器:
TIM_Cmd(TIM3,ENABLE);
编写中断服务函数
使能中断之后,如果定时时间到,就会跳转到中断处理函数里面执行。需要编写中断处理函数。首先是中断函数名,这个是固定的,在 startup_stm32f407_427.s 启动文件中有定义。
宏定义为:
TIM3_IRQHandler // 定时器中断服务函数;
在中断处理函数里需要检测中断标志位是否被置位。
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
这个函数是获取中断标志位。有两个参数,第一个参数就是要检测的定时器外设,第二个参数就是触发的中断源。有一个返回值 FlagStatus,返回值的状态为 SET 和 RESET。需要注意的是每次中断执行完毕之后都需要清除一下中断标志位等待下一次中断发生。
中断服务函数编写代码如下:
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
{ // {}中为中断处理
printf("IRQHandler!!!\r\n");
// 反转LED灯
if( flag == 0 )
{
GPIO_SetBits(GPIOB, GPIO_Pin_2);
flag = 1;
}
else
{
GPIO_ResetBits(GPIOB, GPIO_Pin_2);
flag = 0;
}
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
到此有关定时器中断的配置就完成了。
实验现象
这一章节的代码
在开发板介绍百度网盘链接中:立创·梁山派·天空星STM32F407VET6开发板资料/第03章软件资料/代码例程/008 定时器灯闪烁。
烧写我们的代码之后,每隔 1 秒钟在串口助手上打印一次 IRQHandler!!!,然后 LED 会每隔 1 秒钟时间闪烁一下。