十六、定时器控制灯闪烁
1. 配置流程
一般使用定时器功能,都需要有以下几个步骤。
- 开启时钟(定时器时钟)
- 开启中断
- 配置定时器参数
- 使能中断事件和定时器
- 编写中断服务函数
CW32F030C8T6不同类型的定时器功能不同。这一章就以LED灯1s闪烁为实验内容进行讲解。实现定时1秒钟进入中断,只需要实现基本的定时功能,所有定时器都可以使用,这里就以BTIM1为例进行介绍。
1.1 开启时钟
先来看一下定时器的时钟来源,在数据手册的第6页,如图所示。
从上图可以看到定时器的时钟来源是APB4,而APB4的来源是AHB和APB总线的桥接,完全同步的连接,我们在board_init这个函数初始化的时候设定了主频为64MHz。 所以BTIM1的时钟是64MHz.
开启时钟:
// 使能BTIM1的时钟
__RCC_BTIM_CLK_ENABLE();
2
1.2 开启中断
// 禁止中断,以安全地配置NVIC
__disable_irq();
// 开启BTIM1中断,并关联到NVIC
NVIC_EnableIRQ(BTIM1_IRQn);
// 允许中断,恢复中断状态
__enable_irq();
2
3
4
5
6
7
8
1.3 配置定时器参数
开启时钟之后,需要配置一些定时器的参数,比如定时时间,计数模式等。
关于基本定时器的功能框图如图所示。
要初始化定时器就要先配置这个结构体,首先定义这个结构体
// 定义并初始化定时器时间基准结构体
BTIM_TimeBaseInitTypeDef BTIM_TimeBaseInitStruct;
2
然后配置结构体的参数:
BTIM_Prescaler(预分配系数)
- 类型: uint16_t
- 描述: 此参数决定了定时器时钟的预分频值,用来降低从系统时钟到定时器时钟的频率。取值范围是从2的0次幂到2的15次幂,即2^0 (1) 到 2^15 (32768)。选择合适的预分频系数是根据系统时钟频率和期望的定时器频率(或周期)来决定的。
BTIM_Mode(工作模式)
- 类型: uint16_t
- 描述: 定义定时器的工作模式,这通常涉及到定时器的计数行为。可选的模式包括:
- 00:定时器模式(Timer Mode),在此模式下,定时器在达到预设周期后自动重装载,并继续计数。
- 01:计数器模式(Counter Mode),此模式下定时器作为简单的计数器,计数至最大值后停止,不会自动重装载,除非显式重置。
- 10:触发计数模式(Trigger Counter Mode),此模式下定时器的计数由外部触发信号控制。
- 11:门控计数模式(Gated Counter Mode),计数是否进行受外部门控信号控制,只有门控信号有效时,计数器才工作。
- 取值范围: 两位二进制值,对应上述四种模式。
BTIM_Period(计数重载周期)
- 类型: uint16_t
- 描述: 定义了定时器计数器在达到该值时会自动重装载的周期。换句话说,这是定时器的计数上限。当计数值等于该周期值时,计数器将复位并重新开始计数(如果是在定时器模式下)。取值范围是0x0000到0xFFFF,即0到65535。
- 应用场景: 结合预分频器设置,可以计算出精确的定时周期。
BTIM_OPMode(单次和连续模式控制)
- 类型: uint16_t
- 描述: 此字段控制定时器是执行单次计数(单次模式)还是连续重复计数(连续模式)。尽管这里没有提供具体的枚举或定义细节,通常这类控制会有两种模式:
- 单次模式:定时器仅执行一次计数周期后停止。
- 连续模式:定时器在达到周期后自动重装载,持续计数。
配置好参数之后,就可以初始化定时器了。
关于定时器参数配置和初始化定时器代码如下:
// 配置定时器模式、周期和预分频器
BTIM_TimeBaseInitStruct.BTIM_Mode = BTIM_Mode_TIMER; // 设置为定时器模式
BTIM_TimeBaseInitStruct.BTIM_Period = 62500 - 1; // 设置周期,使得定时器每1秒产生一次溢出中断
BTIM_TimeBaseInitStruct.BTIM_Prescaler = BTIM_PRS_DIV1024; // 预分频器设置为1024,以降低时钟频率至适合1s定时
2
3
4
我们是如何算出62500这个值的呢?
首先我们看手册知道了基本定时器的时钟频率是64MHz,也就是64000000。
我们选择一个合适的预分频系数,是否合适就就看 64000000 / 预分频系数 得到的值不应超出 Period 的范围。
这里我们选择1024比较合适!!
- 计算定时器时钟频率:系统时钟64MHz除以预分频系数1024,得到的定时器时钟频率为:
- 设定周期(Period)以得到1秒中断:为了使定时器每秒产生一次中断,我们需要设置其计数周期,使得在该周期内计数器从0计数到Period时恰好为1秒。因此,周期值应等于定时器时钟频率的倒数,即:
- 计算实际Period值:既然我们知道了每16微秒计数一次,那么1秒(即1,000,000微秒)对应的计数次数为:
所以我们设定为62500!!!
万能公式:
- 系统时钟频率(f_clk): 定时器的输入时钟频率,例如64MHz。
- 预分频系数(Prescaler): 定时器时钟频率除以此值得到定时器的实际工作频率。
- 计数周期(Period): 定时器计数器从0计数到这个值时将产生更新事件或中断。
- 定时时间(T): 期望的定时时间,比如1秒。
接下来我们初始化定时器配置
// 使用上述配置初始化定时器BTIM1
BTIM_TimeBaseInit(CW_BTIM1, &BTIM_TimeBaseInitStruct);
2
1.4 使能中断事件和定时器
使能中断:
// 使能BTIM1的溢出中断
BTIM_ITConfig(CW_BTIM1, BTIM_IT_OV, ENABLE);
2
使能BTIM1定时器
// 启动定时器BTIM1
BTIM_Cmd(CW_BTIM1, ENABLE);
2
1.5 编写中断服务函数
使能中断之后,如果定时时间到,就会跳转到中断处理函数里面执行。需要编写中断处理函数。首先是中断函数名,这个是固定的,在启动文件中有定义。
BTIM1_IRQHandler// 定时器中断服务函数;
在中断处理函数里需要检测中断标志位是否被置位。
ITStatus BTIM_GetITStatus(BTIM_TypeDef *BTIMx, uint16_t BTIM_FLAG)
这个函数是获取中断标志位。有两个参数,第一个参数就是要检测的定时器外设,第二个参数就是触发的中断源。有一个返回值ITStatus,返回值的状态为SET和RESET。需要注意的是每次中断执行完毕之后都需要清除一下中断标志位等待下一次中断发生。
中断服务函数编写代码如下:
/******************************************************************
* 函 数 名 称:BTIM1_Init
* 函 数 说 明:定时器BTIM1中断服务函数
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void BTIM1_IRQHandler(void)
{
// 判断是否为溢出中断
if (BTIM_GetITStatus(CW_BTIM1, BTIM_IT_OV))
{
// 清除溢出中断标志
BTIM_ClearITPendingBit(CW_BTIM1, BTIM_IT_OV);
// 打印信息到串口,用于调试观察中断触发
printf("BTIM1_IRQHandler\r\n");
// 切换LED状态(如果之前是亮,则灭;之前是灭,则亮)
PC13_TOG();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
到此有关定时器中断的配置就完成了。
2.实验现象
关于这一章节的代码,在立创·地文星CW32F030C8T6开发板资料/第03章软件资料/代码例程/007定时器灯闪烁。
烧写我们的代码之后,每隔1秒钟在串口助手上打印一次BTIM1_IRQHandler,然后LED会每隔1秒钟时间闪烁一下。