第0章 定时器的公式
公式中的时间表示的是定时器的时间周期,即定时器从计数开始到溢出的时间间隔。
预分频值设置的越大,表示计数越慢,溢出越慢。
预分频值设置的越小,表示计数越快,溢出越快。精度变高
0.1 stm32F407的定时器3产生2s钟的更新中断
#include "stm32f4xx.h"
void TIM3_Int_Init(uint16_t prescaler, uint16_t period)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1. 使能 TIM3 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
// 2. 配置 TIM3 基本参数
TIM_TimeBaseStructure.TIM_Period = period - 1; // 自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler = prescaler - 1; // 预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数模式:向上计数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// 3. 使能 TIM3 更新中断
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
// 4. 配置 NVIC(中断优先级)
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 5. 使能 TIM3
TIM_Cmd(TIM3, ENABLE);
}
// TIM3 中断处理函数
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); // 清除中断标志
// 这里写 2s 触发的任务
GPIO_ToggleBits(GPIOD, GPIO_Pin_12); // 切换 LED 状态
}
}
// 初始化 TIM3,2s 触发
TIM3_Int_Init(8400, 20000);
第一章 定时器的应用场景
- 定时功能----------------延时功能
- 输出比较功能-----------输出PWM波
- 输入捕获功能-----------计算输入波形的周期
第二章 定时器的原理
2.1 定时器的计数原理
1. 定时器的本质是一个计数器;
2. 计数器是对输入的系统频率信号进行计数;
3. 每来一个周期的信号,计数器的cnt 加一。如果周期T表示为1s,来三个周期就表示3s时间到达;
如果系统时钟频率是120MHz,不进行预分频直接给计数器计数,那么周期为T = (1/120)us。计数器记录120次,表示t = 120 * T us = 1us时间到;计数器记录240次,表示t = 240 * T us = 2us时间到;
2.2 定时器的自动重装载器
一方面假设系统时钟频率是120MHz,不进行预分频直接给计数器计数,那么周期为T = (1/120)us。另一方面设置CAR寄存器的值为12000。每当计数器来一个脉冲(来一个周期T的信号),计数器的的值会自加1操作,并且将自加后的结果与CAR寄存器的值进行比较。
比较之后,如果发现没有到达设定的CAR寄存器值,则继续来脉冲累加。
如果计数器累加到12000次,达到了设定的CAR寄存器值,此时表示t = 12000 * T us = 100us时间到,会产生一个中断(进入中断函数)。
2.3 定时器的预分频器
假定预分频器的值设置为120,总线给定时器的时钟频率为120MHZ;
在定时器的预分频器(Prescaler)设置为 120 的情况下,输入时钟频率为 120 MHz 时,预分频器会对输入的时钟信号进行分频。具体分频过程如下:
- 预分频器的功能:它的作用是将高频的输入时钟信号降低到适合计数器工作的频率。
- 预分频器设置为 120:意味着预分频器需要 120 个输入脉冲 才会输出 1 个脉冲。
- 输出到计数器的频率:输入时钟频率为 120 MHz,经过预分频器分频后,输出频率为:
f = 120MHz / 120 = 1 MHz
因此,输入到预分频器的 120 个脉冲,会转换成输出的 1 个脉冲,然后这个脉冲才会送到定时器的计数器。
总结:
- 预分频器 将输入的 120 MHz 时钟分频为 1 MHz。
- 计数器 以 1 MHz 的频率进行计数。
那么输入120个脉冲到预分频器后,才产生1个脉冲给计数器
2.4 定时器的硬件结构
2.5 定时器的时钟树
2.6 通用定时器和高级定时器
- 最直观的区别是通道个数;
第三章 定时器输出PWM
3.1 应用举例
3.2 输出比较功能
3.2.1 GD32
- 如果T = 1us,周期为1us。每来一个脉冲计数器加一,并且将累计后的值CNT和输出比较值比较。如果小于输出比较值,输出高电平;如果大于输出比较值,输出低电平;
- 如果累加值累加到自动重装载值后,就会触发自动重载操作,将计数器中的累加值CNT置为为0,重新开始累加。
-
定时器配置
3.2.2 stm32F4
注意:
- PF9口本来是一个普通的GPIO口,现在需要配置为定时器14的通道1,所以需要将IO口设置为复用功能;
- 配置定时器14输出100HZ的频率(周期为10ms),
3.3 输入捕获功能----测量信号的周期频率或脉冲宽度
3.3.1 输入捕获的原理一
- 预分配一直给计数器周期性的脉冲,并且假定周期为T = 1us;
- 计数器每来一个脉冲,计数器中的CNT累加1。
- 突然某一个时刻,GPIO口的电平发生跳变(假定是一个上升沿),此时会触发捕获事件以及和一个中断事件。
- 捕获事件将计数器中的值保存到输入捕获寄存器中保存着;
- 中断事件(中断函数中)将输入捕获寄存器中的值取出等待使用,假定为V1;
- 突然某一个时刻,GPIO口的电平又发生跳变(假定是一个上升沿),此时会触发捕获事件以及和一个中断事件。
- 捕获事件将计数器中的值保存到输入捕获寄存器中保存着;
- 中断事件(中断函数中)将输入捕获寄存器中的值取出,假定为V2;
中断中将V2 - V1得到一个差值V3;V3 * T = V3 * 1us的结果就是输入的周期频率。
3.3.2 输入捕获的原理二
- 预分配一直给计数器周期性的脉冲,并且假定周期为T = 1us;
- 计数器每来一个脉冲,计数器中的CNT累加1。
- 突然某一个时刻,GPIO口的电平发生跳变(假定是一个上升沿),此时会触发捕获事件以及和一个中断事件。
- 捕获事件将计数器中的值保存到输入捕获寄存器中保存着;
- 中断事件(中断函数中)将计数器中的值第一次置位为0;
- 突然某一个时刻,GPIO口的电平又发生跳变(假定是一个上升沿),此时会触发捕获事件以及和一个中断事件。
- 捕获事件将计数器中的值保存到输入捕获寄存器中保存着;
- 中断事件(中断函数中)将计数器中的值读取出来为V1;
中断中将V1 * T = V1 * 1us的结果就是输入捕获的周期信号的频率。
3.3.3 代码示例;
project/
├── main.c // 主函数入口
├── tim_capture.c // 定时器输入捕获功能实现
├── tim_capture.h // 定时器输入捕获功能声明
1. 头文件 (tim_capture.h
)
#ifndef TIM_CAPTURE_H
#define TIM_CAPTURE_H
#include "stm32f10x.h"
// 捕获功能初始化函数
void TIM2_InputCapture_Init(void);
// 获取捕获的周期(单位: 微秒)
uint32_t TIM2_GetPeriod(void);
#endif // TIM_CAPTURE_H
2. 源文件 (tim_capture.c
)
#include "tim_capture.h"
// 全局变量,存储捕获结果
static volatile uint16_t capture1 = 0, capture2 = 0; // 捕获值
static volatile uint32_t period = 0; // 信号周期
static volatile uint8_t captureFlag = 0; // 捕获完成标志
// 定时器输入捕获初始化
void TIM2_InputCapture_Init(void) {
// 1. 启用时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // TIM2 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // GPIOA 时钟
// 2. 配置 GPIO
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; // PA0: TIM2_CH1
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 3. 配置定时器
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_TimeBaseStruct.TIM_Period = 0xFFFF; // 最大计数值
TIM_TimeBaseStruct.TIM_Prescaler = 72 - 1; // 定时器分频为 72MHz / 72 = 1MHz
TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
// 4. 配置输入捕获
TIM_ICInitTypeDef TIM_ICInitStruct;
TIM_ICInitStruct.TIM_Channel = TIM_Channel_1; // 捕获通道 1
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; // 捕获上升沿
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI; // 直接输入
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 无分频
TIM_ICInitStruct.TIM_ICFilter = 0x0; // 无滤波
TIM_ICInit(TIM2, &TIM_ICInitStruct);
// 5. 使能中断
TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE); // 使能捕获比较中断
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
// 6. 启动定时器
TIM_Cmd(TIM2, ENABLE);
}
// 获取捕获周期
uint32_t TIM2_GetPeriod(void) {
return period; // 返回测量的信号周期
}
// 定时器中断处理函数
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) {
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); // 清除中断标志
if (captureFlag == 0) {
capture1 = TIM_GetCapture1(TIM2); // 第一次捕获
captureFlag = 1;
} else if (captureFlag == 1) {
capture2 = TIM_GetCapture1(TIM2); // 第二次捕获
if (capture2 > capture1) {
period = capture2 - capture1; // 正常情况
} else {
period = (0xFFFF - capture1 + capture2 + 1); // 计数器溢出情况
}
captureFlag = 0; // 重置标志
}
}
}
3. 主函数 (main.c
)
#include "stm32f10x.h"
#include "tim_capture.h"
int main(void) {
SystemInit(); // 初始化系统时钟
TIM2_InputCapture_Init(); // 初始化 TIM2 输入捕获功能
while (1) {
// 检查是否已经测量到信号周期
uint32_t signalPeriod = TIM2_GetPeriod();
if (signalPeriod != 0) {
// 计算信号频率 (Hz)
float frequency = 1000000.0f / signalPeriod; // 1 MHz 时钟基准
// 用户可添加进一步处理,如显示频率或其他逻辑
}
}
}
触发中断的条件
根据代码和 STM32 的定时器捕获功能,以下条件会触发这个中断:
-
TIM2 的输入捕获通道 1(CC1)检测到信号边沿跳变。
- 在定时器配置时,将 CC1 通道配置为输入捕获模式,并设置边沿触发类型(上升沿、下降沿或双边沿)。
- 例如,信号从低到高(上升沿)或高到低(下降沿)跳变时,捕获触发。
-
中断使能:
- 必须启用 TIM2 的 CC1 通道中断:
TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);
- 必须启用 TIM2 的 CC1 通道中断:
-
中断标志位被置位:
- 捕获事件发生时,定时器的 CC1 中断标志位(
TIM_IT_CC1
)会被置位,随后触发TIM2_IRQHandler
。
- 捕获事件发生时,定时器的 CC1 中断标志位(
所以输入信号的周期为:
T = capture2 - capture1; // 周期的单位是微秒
所以输入信号的频率frequency (Hz) f = (1 / T ) * 10^6 Hz
3.4 定时器作为延时函数
- 假定总线给定时器的时钟频率为84MHz时,并且设置预分频器的预分频值为84;那么产生的时钟频率f = 1MHz。
- T = 1us,如果设置自动重装载值为65535,并且设置定时器为向上计数。
- 计数器来一个周期为1us的脉冲,计时器加1;
#include "drv_timer1.h"
void InitDelayTime(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* 定时器1时钟使能 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
/* 自动重装载寄存器周期的值 */
TIM_TimeBaseStructure.TIM_Period = 0XFFFF - 1; // 设置定时器溢出值(65535)
/* 预分频器配置,将定时器时钟分频为1 MHz (每微秒1个周期) */
TIM_TimeBaseStructure.TIM_Prescaler = ((SystemCoreClock / 2) / 1000000) -1; //
/* 时钟分割 */
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
/* 向上计数 */
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
/* 初始化定时器 */
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
/* 先关闭定时器 */
TIM_Cmd(TIM1, DISABLE);
}
/* 等待一段时间,单位us */
void hwDelayTimeus(DWORD dwDelay) {
/* 确保延迟时间不超过16位计数器的范围 */
DWORD dwTime = dwDelay & 0xFFFF;
/* 使能定时器 */
TIM_Cmd(TIM1, ENABLE);
/* 清除定时器更新标志 */
TIM_ClearFlag(TIM1, TIM_FLAG_Update);
/* 设置定时器初始计数器值 */
TIM_SetCounter(TIM1, 0);
/* 等待定时器溢出(即计数器到达TIM_Period) */
while (TIM_GetCounter(TIM1) < dwTime);
/* 禁用定时器 */
TIM_Cmd(TIM1, DISABLE);
}
/* 等待一段时间,单位ms */
void hwDelayTimems(DWORD dwDelay) {
while (dwDelay--) {
hwDelayTimeus(1000); // 每次延时1毫秒(1000微秒)
}
}
1. 定时器初始化时,只是配置了电气属性,并没有打开定时器,需要用的时候才打开。这样不耗电。
2. 需要用的时候设置定时器的开始计数值为0;
3. 获取计数器的值做比较,实现延时功能;
4. 时间到了就关闭定时器。