十七、定时器控制灯闪烁
1. 配置流程
一般使用定时器功能,都需要有以下几个步骤。
- 开启时钟(定时器时钟)
- 配置定时器参数
- 配置中断优先级
- 使能中断事件和定时器
- 编写中断服务函数
STM32F103C8T6不同类型的定时器功能不同。这一章就以LED灯1s闪烁为实验内容进行讲解。实现定时1秒钟进入中断,只需要实现基本的定时功能,所有定时器都可以使用,这里就以定时器3为例进行介绍。
2. 开启时钟
先来看一下定时器的时钟来源,在数据手册的第19页,如图所示。
从上图可以看到定时器的时钟来源主要分为两部分,第一部分来源于APB1,第二部分来源于APB2。APB1最大36MHz,APB2最大72MHz。
这里使用TIMER3,就要先使能TIMER3的时钟,又因为TIMER3时钟来源于APB1,APB1的时钟在system_stm32f10x.c
中定义。
⚠
对于APB1
时钟来说,APB1
是AHB
总线的2分频率也就是 72 / 2 = 36,定时器时钟拥有一个x2的倍频
会将当前总线APB1频率x2
,将x2后的时钟频率作为挂载在APB1总线上定时器的时钟频率
。
所以TIMER3的时钟是72MHz
!!!
开启时钟:
// 使能TIM3时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
2
3. 配置定时器参数
开启时钟之后,需要配置一些定时器的参数,比如定时时间,计数模式等。
关于基本定时器的结构框图如图所示:
要初始化定时器就要先配置这个结构体,首先定义这个结构体:
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 - 1;
TIM_TimeBaseStructure.TIM_Prescaler = 7200 - 1; // TIMER3时钟频率72MHz
2
3
4
5
使能定时器配置:
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // 使能TIM3配置
4. 配置中断优先级
定时器的参数配置好之后,定时器基本就配置好了。不过我们需要在中断函数中去执行对应的功能,就要对中断进行操作。前面介绍过,如果要使用中断功能,就需要配置中断优先级。中断分组还继续沿用之前的配置。
配置TIMER3中断的结构体:
NVIC_InitTypeDef NVIC_InitStructure;
参数解释
NVIC_IRQChannel
:这个成员用于指定要配置的中断请求(IRQ)通道。TIM3_IRQn是定时器3的中断请求编号,表示这次配置是针对定时器3的中断。NVIC_IRQChannelCmd
:该成员用于启用或禁用指定的中断通道。ENABLE表示启用定时器3的中断。NVIC_IRQChannelPreemptionPriority
:这个成员设置中断的抢占优先级。在STM32中,较低的数值表示较高的优先级。0x01表示此中断的抢占优先级设置为1。抢占优先级用于在多个中断同时请求时决定哪个中断先被执行。NVIC_IRQChannelSubPriority
:设置中断的子优先级。在同一抢占优先级内,子优先级决定了中断的优先顺序。0x03表示此中断的子优先级为3。子优先级在同一抢占优先级的中断之间决定优先顺序。
配置TIMER3中断参数:
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; // 选择TIM3中断
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
2
3
4
5. 使能中断事件和定时器
使能中断:
NVIC_Init(&NVIC_InitStructure); // 使能NVIC配置
TIM_ITConfig(TIM3,TIM_IT_Update, ENABLE); // 使能TIM3中断
2
3
使能TIM3定时器:
TIM_Cmd(TIM3, ENABLE); //使能TIM3
6. 编写中断服务函数
使能中断之后,如果定时时间到,就会跳转到中断处理函数里面执行。需要编写中断处理函数。首先是中断函数名,这个是固定的,在启动文件中有定义。
宏定义为:
TIM3_IRQHandler // 定时器中断服务函数
在中断处理函数里需要检测中断标志位是否被置位。用到的函数是:
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
这个函数是获取中断标志位。有两个参数,第一个参数就是要检测的定时器外设,第二个参数就是触发的中断源。有一个返回值FlagStatus
,返回值的状态为SET
和RESET
。需要注意的是每次中断执行完毕之后都需要清除一下中断标志位等待下一次中断发生。
中断服务函数编写代码如下:
⚠警告
在中断处理程序中执行printf
是不推荐的,这里是为了更直观的看到中断服务函数的运行,所以这样写。在自己的程序中不推荐使用!
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) // {}中为中断处理
{
printf("IRQHandler!!!\r\n");
// 反转LED灯
if( flag == 0 )
{
GPIO_SetBits(GPIOC, GPIO_Pin_13);
flag = 1;
}
else
{
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
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
到此有关定时器中断的配置就完成了。
7.实验现象
关于这一章节的代码百度网盘下载,在立创·STM32F103C8T6开发板资料(标准库)/第03章软件资料/代码例程/008定时器灯闪烁。
烧写代码之后,每隔1秒钟在串口助手上打印一次 IRQHandler!!!,然后LED会每隔1秒钟时间闪烁一下。