第一章 定时器的应用场景
第二章 定时器的原理
2.1 定时器的计数原理
1. 定时器的本质是一个计数器;
2. 计数器是对输入的系统频率信号进行计数;
3. 每来一个周期的信号,计数器的cnt 加一。如果周期T表示为1s,来三个周期就表示3s时间到达;如果系统时钟频率是120MHz,那么周期为T = (1/120)us,记录120次,t = 120 * T us = 1us时间到;
2.2 定时器的计数上限
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 定时器利用输出比较产生PWM控制LED
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 定时器输出定时功能
- 假定总线给定时器的时钟频率为120MHz时,并且设置预分频器的预分频值为120;那么产生的时钟频率f = 1MHz。
- T = 1us,如果设置自动重装载值为600,并且设置定时器为向下计数。那么当从600递减为0时,定时器会产生一个(1us * 600)的600us中断,表示600us时间到达。