第25章 基本定时器定时实验
第二十五章 基本定时器定时实验
1. 硬件设计
本实验利用基本定时器 TIM6 定时 1s,1s 时间到 LED 翻转一次。基本定时器是单片机内部的资源, 没有外部 IO,不需要接外部电路,现只需要一个 LED 即可。
2. 软件设计
2.1 编程大纲
-
TIM相关参数宏定义
-
TIM工作参数设置及初始化
-
更新中断服务函数
-
主函数测试
通用定时器和高级定时器的定时编程要点跟基本定时器差不多,只是还要再选择下计数器的计数模式, 是向上还是向下。因为基本定时器只能向上计数,且没有配置计数模式的寄存器,默认是向上。
2.2 代码分析
2.2.1 TIM相关参数宏定义
/* 定时器基础配置 */
#define TIM_BASE TIM6
#define TIM_BASE_Period 999// ARR值
#define TIM_BASE_Prescaler 71 // 预分频系数
/* 中断相关 */
#define TIM_BASE_IRQ TIM6_IRQn
#define TIM_BASE_IRQHandler TIM6_IRQHandler
2.2.2 基本定时器配置
#include "TIMbase.h"
void TIM_BASE_Init(void)
{
/* NVIC相关配置 */
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); // 设置中断优先组
NVIC_InitStructure.NVIC_IRQChannel = TIM_BASE_IRQ; // 设置中断源
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 设置抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 设置子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断
NVIC_Init(&NVIC_InitStructure); // 初始化NVIC
/* TIM相关配置 */
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
TIM_TimeBaseStructure.TIM_Period = TIM_BASE_Period; // ARR = 999
TIM_TimeBaseStructure.TIM_Prescaler= TIM_BASE_Prescaler; // 预分频值71
TIM_TimeBaseInit(TIM_BASE, &TIM_TimeBaseStructure);
TIM_ClearFlag(TIM_BASE, TIM_FLAG_Update); // 清除中断标志位
TIM_ITConfig(TIM_BASE, TIM_IT_Update, ENABLE); // 开启计数器中断
TIM_Cmd(TIM_BASE, ENABLE);
}
/* 具体计算过程
定时器时钟源为APB1,APB1为72MHz
定时器的频率为72/(71+1) = 1MHz
定时器的计数周期为999+1,计数周期/定时器频率 = 1ms
*/
这里出现了几个新的库函数,我们来解释一下:
1. TIM_TimeBaseInit
原型:
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_InitStruct);
功能:
- 初始化指定定时器的时间基准(计数周期和预分频器)。
TIMx
是要配置的定时器外设,例如 TIM2、TIM3 等。TIM_InitStruct
是一个TIM_TimeBaseInitTypeDef
结构体,包含了计数器的各种参数,如计数周期(TIM_Period
)、预分频器(TIM_Prescaler
)、时钟分频(TIM_ClockDivision
)和计数模式(TIM_CounterMode
)
结构体定义:
typedef struct
{
uint16_t TIM_Period; // 自动重载寄存器的值
uint16_t TIM_Prescaler; // 预分频器的值
uint16_t TIM_ClockDivision; // 时钟分频
uint16_t TIM_CounterMode; // 计数模式
} TIM_TimeBaseInitTypeDef;
2. TIM_ClearFlag
原型:
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
功能:
- 清除指定定时器的标志位。
TIM_FLAG
是标志位参数,例如TIM_FLAG_Update
。 - 该函数用于重置定时器的状态标志,以便在下一次事件发生时能够重新捕捉。
标志位定义:
#define TIM_FLAG_Update ((uint16_t)0x0001) // 更新中断标志位
3. TIM_ITConfig
原型:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
功能:
- 配置定时器的中断。
TIM_IT
是中断源,例如TIM_IT_Update
。 NewState
可以是ENABLE
或DISABLE
,用于启用或禁用中断。
参数说明:
TIMx
:要配置的定时器外设。TIM_IT
:中断源。NewState
:设置中断状态,ENABLE
启用,DISABLE
禁用。
4. TIM_Cmd
原型:
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
功能:
- 启动或停止定时器。
NewState
可以是ENABLE
或DISABLE
。 - 启用定时器使其开始计数,或禁用定时器停止计数。
参数说明:
TIMx
:要配置的定时器外设。NewState
:设置定时器状态,ENABLE
启用,DISABLE
禁用。
在初始化定时器的时候,我们定义了一个结构体:TIM_TimeBaseInitTypeDef, 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;
其中 TIM15/16/17 只存在与互联型产品中,在 F1 大/中/小容量型号中没有。
2.2.3 定时器中断服务函数
void TIM_BASE_IRQHandler(void)
{
if(TIM_GetITStatus(TIM_BASE, TIM_IT_Update) != RESET) // 如果定时器产生计数溢出,也就是产生了中断
{
time++; // 1ms
TIM_ClearITPendingBit(TIM_BASE, TIM_IT_Update); // 清除中断标志位
}
}
定时器中断一次的时间是 1ms,我们定义一个全局变量 time,每当进一次中断的时候,让time 来记录进入中断的次数。 如果我们想实现一个 1s 的定时,我们只需要判断 time 是否等于 1000 即可,1000 个 1ms 就是 1s。 然后把 time 清0,重新计数,以此循环往复。在中断服务程序的最后,要把相应的中断标志位清除掉,切记。
解释一下中断函数中出现的新库函数:
1. TIM_GetITStatus
原型:
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
功能:
- 检查指定定时器的特定中断标志位是否被置位。
TIMx
是指定的定时器外设(例如TIM2
、TIM3
等)。TIM_IT
是要检查的中断源,例如TIM_IT_Update
,表示更新中断。
返回值:
- 如果指定的中断标志位被置位,返回
SET
。 - 否则,返回
RESET
。
2. TIM_ClearITPendingBit
原型:
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
功能:
- 清除指定定时器的特定中断挂起标志位。
TIMx
是指定的定时器外设。TIM_IT
是要清除的中断源,例如TIM_IT_Update
,表示更新中断。
作用:
- 在中断服务程序中调用该函数可以将中断挂起标志位清除,从而避免重复触发同一中断。
2.2.4 主函数测试
#include "stm32f10x.h"
#include "led.h"
#include "TIMbase.h"
volatile uint32_t time = 0; // ms 计时变量
int main(void)
{
LED_Init();
TIM_BASE_Init();
while(1)
{
if(time == 1000) // 1000 * 1 ms = 1s
{
time = 0;
LED1_TOGGLE();
}
}
}
主函数做一些必须的初始化,然后在一个死循环中不断的判断 time 的值,time的值在定时器中断改变, 每加一次表示定时器过了 1ms,当 time 等于 1000 时,1s时间到,LED1翻转一次,并把 time 清 0。
3. 小结
TIM基本定时器还算简单,合适的配置就行,下面我们来回顾一下基本流程:
3.1 配置基本定时器
3.1.1 使能定时器时钟
在开始配置定时器之前,你需要确保定时器的时钟已经被启用。这通常通过配置系统时钟(RCC)来完成。
// 使能定时器时钟,例如使能 TIM2
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
3.1.2 配置定时器基础参数
你需要设置定时器的预分频器、计数周期等参数。预分频器用于分频定时器时钟,计数周期用于设定定时器溢出的时间间隔。
TIM_TimeBaseInitTypeDef TIM_InitStructure;
// 配置定时器基础参数
TIM_InitStructure.TIM_Period = 9999; // 计数周期值(计数器溢出值)
TIM_InitStructure.TIM_Prescaler = 71; // 定时器时钟预分频值
TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分割
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数器向上计数
// 初始化定时器
TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
3.2 配置中断
3.2.1 使能定时器更新中断
启用定时器的更新中断,使得定时器溢出时可以触发中断请求
// 使能定时器更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
3.2.2 配置中断优先级和使能中断
在 NVIC(嵌套向量中断控制器)中配置中断优先级,并使能定时器的中断请求。
NVIC_InitTypeDef NVIC_InitStructure;
// 配置中断优先级
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
// 初始化 NVIC
NVIC_Init(&NVIC_InitStructure);
3.3 启动定时器
最后,你需要启动定时器,使其开始计数。
// 启动定时器
TIM_Cmd(TIM2, ENABLE);
3.4 编写中断服务函数
在中断服务程序中处理定时器溢出的事件。你需要确保在中断服务程序中清除中断挂起标志位。
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
// 执行定时器溢出后的任务
// 例如:改变 LED 状态、更新计时器等
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
2024.9.10 第一次修订,后期不再维护
2025.1.23 优化内容和代码
本文作者:hazy1k
本文链接:https://www.cnblogs.com/hazy1k/p/18407125
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步