STM32之通用定时器详解

一、通用定时器

1.通用定时器描述

一、计数模式:16 位向上 / 向下 / 中央对齐

原理:
  • 向上计数 :计数器从 0 递增到 ARR(自动重装值),溢出时触发更新事件(中断 / DMA)。
  • 向下计数 :计数器从 ARR 递减到 0,下溢时触发更新事件。
  • 中央对齐(向上 / 向下) :计数器先向上到 ARR,再向下到 0,循环往复(对称计数,适合对称 PWM)。
例子:
  • 向上计数 → 定时中断

    配置 TIM3 为向上计数,ARR=999PSC=71(假设系统时钟 72MHz,分频后计数器频率为 72MHz/(71+1)=1MHz,计数周期 1μs)。

    (999+1)×1μs=1ms 触发一次更新中断,用于 1ms 定时任务(如 PID 控制周期、传感器采样)。

  • 中央对齐 → 对称 PWM

    三相电机驱动需对称 PWM (减少谐波),配置 TIM1(高级定时器,但通用定时器也支持中央对齐)为中央对齐,ARR=999CCR=500(占空比 50%)。

    计数器先从 0→999(向上),到 500 时翻转电平;再从 999→0(向下),到 500 时再次翻转,形成对称方波,降低电机电磁噪声。

二、16 位可编程预分频器(PSC)

原理:

预分频器 PSC 决定分频系数1~65536,公式:计数器频率 = TIMx_CLK / (PSC + 1)),用于调整计数速度。

例子:
  • 低速定时需求
    若需计数器每 10ms 递增 1 次(频率 100Hz),系统时钟 72MHz,则:
    PSC + 1 = 72MHz / 100Hz = 720000 → 但 PSC 是 16 位(最大 65535),无法直接实现。
    拆分方案 :先用 PSC=719(分频 100 倍,72MHz→720KHz),再设 ARR=999(周期 (999+1)×(1/720KHz)≈1.388ms),通过多次计数累加实现 10ms 定时(如累计 7 次更新事件)。

三、4 个独立通道:输入捕获 / 输出比较 / PWM / 单脉冲

1. 输入捕获 → 测量脉冲 / 频率

原理:捕获外部信号的边沿(上升 / 下降沿),记录计数器值,计算时间差。

例子:测量红外信号频率(如遥控器波形):

  • 配置 TIM4_CH1 为输入捕获,上升沿触发。
  • 第一次捕获:记录计数器值 CCR1(信号上升沿时刻)。
  • 第二次捕获:记录 CCR2(下一个上升沿时刻)。
  • 频率 = 1 / [(CCR2 - CCR1) × 计数周期]
    若计数周期 1μs,CCR2-CCR1=1000 → 频率 1KHz。
2. 输出比较 → 精准电平翻转

原理:计数器与 CCR(比较值)匹配时,触发电平翻转 / 中断

例子:LED 精准闪烁(500ms 周期)

  • 配置 TIM2_CH2 为输出比较,ARR=49999(计数频率 1KHz,周期 1ms),CCR=25000
  • 当计数器到 25000 时,电平翻转(250ms 时翻转),实现 500ms 周期闪烁(250ms 亮,250ms 灭)。
3. PWM 生成 → 电机调速 / LED 调光

原理:通过 ARR 定周期,CCR 定占空比,生成方波。

例子:直流电机调速(边沿对齐 PWM)

  • 配置 TIM5_CH3 为 PWM 输出,ARR=999(周期 1ms),CCR=300(占空比 30%)。
  • 电机功率与占空比成正比,30% 占空比对应低速运行;若调至 80%,电机加速。
4. 单脉冲模式 → 触发单次动作

原理:外部触发(如 GPIO 上升沿)后,输出一个固定宽度的脉冲 (由 CCR 决定)。

例子:触发 ADC 单次采样

  • 配置 TIM1_CH4 为单脉冲模式,CCR=100(脉冲宽度 100× 计数周期)。
  • 当外部触发信号到来,输出 10μs 脉冲,启动 ADC 转换(确保采样窗口精准)。

四、同步电路:多定时器协同

原理:通过 TRGO(触发输出)→ TRGI(触发输入) 连接,实现多定时器同步启动 / 停止。

例子:双轴运动控制同步触发

  • TIM3 为主定时器,TRGO 输出连接 TIM2 的 TRGI 输入。
  • TIM3 启动时,TIM2 同步启动,保证两个轴的运动时间对齐(如机械臂两关节同时动作)。

五、中断 / DMA 触发:事件驱动

定时器可在以下事件触发中断或 DMA:

  • 更新事件:计数器溢出 / 下溢(如定时采集传感器)。
  • 触发事件:定时器启动 / 停止(如记录电机启动时间戳)。
  • 输入捕获:捕获到外部脉冲(如测量高频信号)。
  • 输出比较:比较匹配(如定时关闭加热管)。

例子:10ms 周期采集温度传感器

  • 配置 TIM2 更新中断,每 10ms 触发一次。
  • 在中断服务函数中,读取温度传感器数据,保证采样周期稳定。

六、编码器 / 霍尔传感器支持

1. 编码器接口 → 电机测速 / 方向检测

原理:解码正交编码器的 AB 相脉冲,计数器自动根据边沿增减(上升沿 + 下降沿都计数,精度翻倍)。

例子:电机转速测量

  • 编码器每转输出 1000 个脉冲,配置 TIM2 为编码器模式。
  • 每秒读取计数器值 CNT,转速 = CNT / 1000 转 / 秒(如 1 秒内 CNT=10000→10 转 / 秒)。
2. 霍尔传感器 → 电机换相

原理:解码电机的 霍尔信号(3 路),获取转子位置,触发换相逻辑。

例子:无刷电机换相

  • 霍尔信号(如 011、101 等状态)输入定时器,当检测到状态变化时,切换功率管导通相,实现无刷电机换向

七、硬件约束:16 位 ARR 和 PSC(Cortex-M3 内核)

  • ARR(自动重装值)和 PSC(预分频器)都是16 位寄存器 ,最大取值 65535
  • 若需 超长周期(如 10 秒),需结合软件计数(如累计 1000 次 10ms 更新事件),或使用 32 位定时器(如 TIM2、TIM5 在 STM32F1 中是 32 位计数器,突破 ARR 的 16 位限制,但 PSC 仍为 16 位)。

2. 通用定时器框图分析

一、模块作用拆解

1. CNT 计数器
  • 是定时器的 "心跳",实时递增 / 递减计数 (由定时器时钟驱动,受 PSC 分频控制)。
  • 比如配置为向上计数,CNT 会从 0 一直数到 ARR(自动重装值),再回到 0 循环。
2. 捕获 / 比较寄存器(以通道 1 为例)
  • 存一个目标数值 (比如 75-1=74 ),用来和 CNT 的实时值做比较
  • 它是 "判定条件" 的核心:CNT 实时值是否满足条件,全看和这个寄存器的数值关系。
3. OC1REF(输出参考信号)
  • 比较的中间结果 :当 CNT 满足条件(如 CNT < 捕获/比较寄存器值 ),OC1REF 输出高电平;不满足则输出低电平。
  • 注意:这是内部逻辑信号,还没到实际引脚!
4. 输出控制(Output Control)
  • 决定最终引脚输出(TIMx_CH1)的实际电平 ,会结合 CCER 寄存器的极性配置(高 / 低电平有效)。
  • 比如:若配置 "高电平有效",则 OC1REF 高电平时,引脚输出高电平;若配置 "低电平有效",则 OC1REF 高电平时,引脚输出低电平(翻转一次)。

二、结合 PWM 例子详细推导

假设需求:生成 占空比 7.5% 的 PWM 信号,参数:

  • ARR = 1000 - 1 = 999(计数器范围 0~999,共 1000 个计数点 )
  • 捕获/比较寄存器 = 75 (目标比较值 )
步骤 1:计数器循环计数

CNT0 开始,以定时器时钟频率 向上计数,直到 999,然后回到 0 重新开始。

  • 假设定时器时钟是 1MHz(周期 1μs ),则 CNT1μs 加 1,数到 999 需要 1000μs(1ms),即 PWM 周期是 1ms
步骤 2:比较逻辑 → 生成 OC1REF

CNT 实时值与 捕获/比较寄存器值(75) 比较:

  • CNT < 75 时(共 75 个计数点:0~74 ):OC1REF = 高电平
  • CNT >= 75 时(共 925 个计数点:75~999 ):OC1REF = 低电平
步骤 3:输出控制 → 引脚实际电平

假设配置 高电平有效CCER 寄存器的 CC1P=0 ):

  • OC1REF 高电平时,引脚 TIMx_CH1 输出高电平
  • OC1REF 低电平时,引脚 TIMx_CH1 输出低电平

最终引脚波形:

  • 高电平持续 75μs0~74 共 75 个计数点,每个点 1μs
  • 低电平持续 925μs75~999 共 925 个计数点 )
步骤 4:占空比计算

占空比 = 高电平时间 / PWM 周期 = 75μs / 1000μs = 7.5%

一、信号流程:外部信号如何进入定时器?

外部信号(比如传感器脉冲、编码器信号)从 TIMx_CH1 引脚输入,经过以下模块处理:
外部信号 → 异或门 → 输入滤波器和边沿检测器 → 预分频器 → 定时器内部

二、核心模块作用拆解

1. 异或门(XOR)
  • 作用:信号极性控制,决定是否翻转输入信号的电平。
  • 应用:比如外部信号是低电平有效,但你想转成高电平触发,就可以通过异或门翻转(配合 TRC 信号实现)。
2. 输入滤波器和边沿检测器
  • 滤波器:滤除高频噪声 ,避免毛刺触发误判。
    • 原理:通过配置采样频率和采样次数,只有持续稳定的电平变化才会被识别。
    • 例子:外部按键抖动(几十 ms 高频抖动),滤波器可以只识别稳定的电平跳变。
  • 边沿检测器:检测上升沿 / 下降沿 ,决定触发事件的条件。
    • 可配置为:仅上升沿触发、仅下降沿触发、上升沿 + 下降沿触发。
3. 预分频器(Prescaler)
  • 作用:分频控制,决定 "几个外部脉冲才触发一次定时器事件"。
  • 例子:
    • 预分频器设为 3,则每 4 个外部脉冲(0~3 计数)才会让定时器内部计数 1 次。
    • 常用于 "降低信号频率,或只记录特定间隔的脉冲"。
4. TI1、TI1FP1、TI1FP2
  • TI1:原始输入信号(未滤波)。
  • TI1FP1滤波后信号(经过滤波器和边沿检测的干净信号)。
  • TI1FP2反向滤波后信号(TI1FP1 的电平翻转版,用于正交解码)。
5. TRC(Trigger Reference Clock)
  • 作用:触发参考信号,可用于异或门的极性控制,或作为同步触发源。
  • 应用:比如让定时器在外部信号上升沿时同步启动计数。

**3.**通用定时器基本功能

操作流程和基础定时器 TIM6 和 TIM7 一致

cpp 复制代码
#include "tim3.h"

// TIM3初始化函数
// psc: 预分频器值
// arr: 自动重装载值
void TIM3_Init(u16 psc, u16 arr)
{
    // 1. 使能TIM3时钟
    RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;  // 置位TIM3时钟使能位
    
    // 2. 关闭定时器,避免配置冲突
    TIM3->CR1 &= ~(0x01);  // 清除CEN位,关闭计数器
    
    // 3. 配置预分频器和自动重装载寄存器
    TIM3->PSC = psc - 1;   // 预分频器配置(值 = 实际分频系数 - 1)
    TIM3->ARR = arr - 1;   // 自动重装载值配置(值 = 实际周期数 - 1)
    
    // 4. 配置计数模式(向上计数)
    TIM3->CR1 &= ~(0x1 << 4);  // 清除DIR位,设置为向上计数
    
    // 5. 使能更新中断
    TIM3->DIER |= 0x01;    // 置位UIE位,使能更新中断
    
    // 6. 开启定时器
    TIM3->CR1 |= 0x01;     // 置位CEN位,使能计数器
    
    // 7. 配置NVIC中断
    NVIC_EnableIRQ(TIM3_IRQn);          // 使能TIM3中断
    NVIC_SetPriority(TIM3_IRQn, 5);     // 设置中断优先级为5
}

// 中断计数器
u16 TIM3_IT_Count = 0;

// TIM3中断服务函数
void TIM3_IRQHandler(void)
{
    // 检查是否是更新中断
    if (TIM3->SR & 0x01)
    {
        // 清除中断标志位
        TIM3->SR &= ~0x01;
        
        // 这里可以添加中断处理逻辑
        TIM3_IT_Count++;  // 计数器加1
        
        // 示例:每1000次中断做一次处理(需根据实际定时周期计算)
        if (TIM3_IT_Count >= 1000)
        {
            TIM3_IT_Count = 0;
            // 执行定时任务...
        }
    }
}
    
1. TIM3_Init 初始化函数核心配置
  • 时钟使能 :通过RCC_APB1ENR_TIM3EN位使能 TIM3 定时器时钟,TIM3 属于 APB1 总线外设

  • 定时器关闭:配置前先关闭计数器(清除 CEN 位),确保寄存器配置可靠

  • 分频与周期配置

    • PSC:预分频器,值为实际分频系数减 1(因为计数器从 0 开始)
    • ARR:自动重装载值,值为实际周期计数减 1
    • 定时周期计算公式:周期 = (PSC + 1) * (ARR + 1) / 定时器时钟频率
  • 计数模式:配置为向上计数模式(默认也是向上计数,这里显式配置使代码更清晰)

  • 中断配置

    • 使能更新中断(UIE 位),当计数器达到 ARR 值时产生中断
    • 通过 NVIC 配置中断使能和优先级
2. 中断服务函数
  • 中断标志检查 :通过SR寄存器的 UIF 位判断是否发生更新中断
  • 标志清除:必须手动清除中断标志位,否则会持续触发中断
  • 中断处理 :通过TIM3_IT_Count计数器记录中断次数,可根据需要添加定时任务逻辑

4. PWM实现呼吸灯

3.4.1 PWM 概述

  • PWM 核心参数
    • 频率:1 秒钟,PWM 输出波形的次数。需依据实际设备应用场景分析确定,不同场景对频率需求不同,如电机控制、LED 调光等场景,合适频率可保障功能稳定与效果。
    • 占空比:一个 PWM 周期内,高电平持续时间与整个 PWM 周期时间的比值,决定了有效电平在周期内的占比,影响输出能量、亮度等。
  • 实现依赖
    需要利用 TIM2 ~ 5 通用定时器完成 PWM 输出,这些定时器具备输出比较、PWM 模式配置等功能,可通过设置预分频器(PSC)、自动重装值(ARR)、比较值(CCR)等寄存器,生成符合参数要求的 PWM 波形 。

3.4.2 AFIO****控制定时器对应输出通道重映射

主要针对不同的 TIM 定时对外输出通道引脚映射关系。

3.4.3 TIM3CH2****寄存器分析

TIM3 CCMR 输入输出模式控制寄存器


3.4.5****核心代码实现

tim3.h:

cpp 复制代码
#include "stm32f10x.h"

// TIM3_CH2(PB5)初始化函数,支持PWM输出
// psc: 预分频器值
// arr: 自动重装载值
void TIM3_CH2_PB5_PWM(u16 psc, u16 arr)
{
    /* 1. 时钟使能 */
    RCC->APB2ENR |= (RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPBEN); // 使能AFIO和GPIOB时钟
    RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;                        // 使能TIM3时钟
    
    /* 2. 关闭定时器,确保配置可靠 */
    TIM3->CR1 = 0;
    
    /* 3. 配置PB5为复用推挽输出 */
    GPIOB->CRL &= ~(0x0F << 20);       // 清除PB5原有配置
    GPIOB->CRL |= (0x0B << 20);        // 1011:复用推挽输出,50MHz
    
    /* 4. TIM3_CH2部分重映射到PB5 */
    AFIO->MAPR &= ~(0x03 << 10);       // 清除原有映射配置
    AFIO->MAPR |= (0x02 << 10);        // 部分重映射:TIM3_CH2->PB5
    
    /* 5. 定时器基础配置 */
    TIM3->CR1 |= (0x01 << 7);          // ARR开启缓冲
    TIM3->CR1 &= ~(0x03 << 5);         // 边缘对齐模式
    TIM3->CR1 &= ~(0x01 << 4);         // 向上计数模式
    TIM3->PSC = psc - 1;               // 预分频配置
    TIM3->ARR = arr - 1;               // 自动重装载值配置
    TIM3->CNT = 0;                     // 计数器清零
    
    /* 6. 配置PWM模式 */
    TIM3->CCMR1 &= ~(0x03 << 8);       // 配置为输出模式(CC2S=00)
    TIM3->CCMR1 |= (0x01 << 10);       // 快速使能(OC2FE=1)
    TIM3->CCMR1 |= (0x01 << 11);       // CCR2预装载使能(OC2PE=1)
    TIM3->CCMR1 |= (0x06 << 12);       // PWM模式1(OC2M=110):CNT<CCR时输出有效电平
    
    /* 7. 配置输出极性(可根据硬件需求切换高低电平有效) */
    TIM3->CCER &= ~(0x03 << 4);        // 清除CC2相关配置
    TIM3->CCER |= (0x01 << 4);         // 使能CH2输出(CC2E=1)
    // TIM3->CCER |= (0x01 << 5);      // 如需低电平有效,取消此注释
    
    /* 8. 开启定时器 */
    TIM3->CR1 |= 0x01;
}

// 设置CCR2值,控制占空比
void TIM3_CH2_PB5_SetCCR(u16 ccr)
{
    TIM3->CCR2 = ccr;
}
    

main.c:

cpp 复制代码
#include "stm32f10x.h"
#include "led.h"
#include "key.h"
#include "delay.h"
#include "tim3.h"

int main(void)
{
    // 初始化外设
    Led_Init();
    Key_Init();
    Delay_Init();
    
    /* 
     * 初始化TIM3_CH2:72分频,ARR=1000
     * 定时器时钟频率 = 72MHz / 72 = 1MHz
     * PWM周期 = (1000) * 1us = 1ms(频率1kHz)
     */
    TIM3_CH2_PB5_PWM(72, 1000);
    
    u16 duty_value = 0;  // 占空比数值(0~1000)
    u8 flag = 0;         // 呼吸灯方向标志
    
    while (1)
    {
        // 设置占空比,控制LED亮度
        TIM3_CH2_PB5_SetCCR(duty_value);
        
        // 呼吸灯逻辑:逐渐变亮→逐渐变暗循环
        if (flag)
        {
            duty_value -= 10;
            if (duty_value <= 0)
                flag = 0;  // 达到最暗,准备变亮
        }
        else
        {
            duty_value += 10;
            if (duty_value >= 1000)
                flag = 1;  // 达到最亮,准备变暗
        }
        
        Delay_Ms(10);  // 延时控制呼吸速度
    }
}
    

重点:GPIO 引脚兼具普通 IO 与复用功能,普通 IO 模式下由代码操作 ODR 等寄存器直接控制引脚输出高低电平(信号从 MCU 到外部设备),而复用模式(如复用推挽、复用开漏)则将引脚控制权移交 MCU 内部集成的外设(如 TIM3 定时器、USART 串口等),此时引脚电平由外设(如定时器生成的 PWM 波形、串口的通信信号)决定,信号流向始终为 MCU 内部外设→引脚→外部设备(如 LED、电机、传感器等),且同一时刻引脚仅能服务于普通 IO 或复用外设中的一种功能,但可通过重新配置 GPIO 模式实现功能切换;定时器等复用外设本质是 MCU 内部硬件模块,需配合引脚作为 "内外桥梁",才能将内部生成的定时、PWM 等信号传递到外部,实现对外部设备的精准控制(如定时中断、PWM 调光调速)
0voice · GitHub