08-SysTick与通用定时器TIM.md 11 KB


tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(进阶篇)V1.0.1 — 第1章:定时器 + 配套代码16~19" author: "尚硅谷研究院" date: 2026-07-15

created: 2026-07-15

SysTick与通用定时器TIM

用生活理解:SysTick 就像厨房里的倒计时秒表——设好时间(LOAD 值),它倒数到 0 就"叮"一声(中断)。TIM 通用定时器就像高级可编程定时器——可以预分频(时间压缩/扩展)、自动重装(倒计时到 0 自动重新开始)、还能输出 PWM 或测量外部脉冲。SysTick 是内核自带的"简单秒表",TIM 是片上外设的"多功能定时器"。


SysTick 系统定时器

SysTick 是 Cortex-M3 内核自带的 24 位递减计数器,属于内核外设,与具体芯片厂商无关。在所有 Cortex-M3 产品间处理方式相同

参考:Cortex-M3 权威指南 §8(SysTick 定时器)

SysTick 寄存器

寄存器 地址偏移 功能
CTRL 0x00 控制与状态寄存器
LOAD 0x04 重装载数值寄存器(24 位有效)
VAL 0x08 当前数值寄存器
CALIB 0x0C 校准寄存器(一般不用)

CTRL 寄存器位说明

名称 值含义 说明
0 ENABLE 1=启动, 0=停止 计数器使能
1 TICKINT 1=允许中断, 0=禁止 计数到 0 时是否产生 SysTick 异常
2 CLKSOURCE 1=AHB(72MHz), 0=AHB/8(9MHz) 时钟源选择
16 COUNTFLAG 自动置 1 计数到 0 时硬件置 1,读后或写 CTRL 时自动清 0

LOAD 寄存器:24 位有效(最大值 16,777,215)。

VAL 寄存器:读返回当前计数值;写任意值清 0 并清除 COUNTFLAG。

SysTick 的两种用法

用法 配置 特点
查询延时 CLKSOURCE=AHB, TICKINT=0 轮询 COUNTFLAG 标志,阻塞式
周期性中断 CLKSOURCE=AHB/8, TICKINT=1 产生 1ms 定时中断(FreeRTOS 时基)

SysTick 延时实现(查询方式)

// 微秒级延时(基于 SysTick 查询,AHB=72MHz)
void Delay_us(uint32_t us)
{
    // LOAD: 72MHz 下每 1us 需要计数 72 次
    // LOAD 最大 24 位 = 16,777,215,单次最大延时 ≈ 233ms
    SysTick->LOAD = 72 * us;

    // VAL: 清当前值(写任何值都清 0,同时清除 COUNTFLAG)
    SysTick->VAL  = 0;

    // CTRL: ENABLE=1, CLKSOURCE=1(AHB), TICKINT=0(无中断)
    // 0x05 = 二进制 0000 0101
    SysTick->CTRL = 0x05;

    // 轮询等待 COUNTFLAG 置 1(计数到 0)
    // SysTick_CTRL_COUNTFLAG = (1UL << 16)
    while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG))
    {
    }

    // 关闭定时器(写 0 到 ENABLE 位)
    SysTick->CTRL &= ~SysTick_CTRL_ENABLE;  // &=~: ENABLE位清0
}

// 毫秒级延时(累加 1000 次微秒)
void Delay_ms(uint32_t ms)
{
    while (ms--)
    {
        Delay_us(1000);  // 1ms = 1000us
    }
}

void Delay_s(uint32_t s)
{
    while (s--)
    {
        Delay_ms(1000);
    }
}

通用定时器 TIM

STM32F103ZET6 有 7 个定时器:

定时器 类型 位数 主要特性
TIM1 高级 16 带互补 PWM 输出、死区插入、刹车功能(电机控制)
TIM2~5 通用 16 时基、PWM、捕获/比较(最常用)
TIM6~7 基本 16 仅时基功能,无外部 I/O

TIM 时基单元结构

APBx 时钟 → PSC(预分频器) → CK_CNT → CNT(计数器) → ARR(自动重装)
                         ↓                       ↓
                      控制计数器时钟          溢出/更新事件(UEV)

关键寄存器: | 寄存器 | 说明 | 范围 | |--------|------|------| | PSC (预分频器) | 对时钟分频,产生 CK_CNT | 16 位(0~65535) | | CNT (计数器) | 当前计数值 | 16 位(0~65535) | | ARR (自动重装载) | 计数到该值时产生溢出 | 16 位(0~65535) | | CR1 (控制寄存器1) | CEN(使能)、UDIS(更新禁止)、URS(更新请求源)、OPM(单脉冲)、ARPE(自动重装预装载) | — | | DIER (中断使能) | UIE(更新中断)、CCxIE(捕获比较中断) | — | | SR (状态寄存器) | UIF(更新标志)、CCxIF(捕获比较标志) | — |

计数器时钟频率计算

CK_CNT = CK_PSC / (PSC + 1)

其中:

  • CK_PSC = APB1 时钟(TIM2~7)或 APB2 时钟(TIM1)
  • 注意:如果 APB1 分频系数 ≠ 1,则 TIM2~7 的时钟 = APB1×2

示例(APB1=36MHz,分频=2 → TIM 时钟=72MHz):

PSC = 7199 → CK_CNT = 72MHz / 7200 = 10KHz (0.1ms/计数)
PSC = 71999 → CK_CNT = 72MHz / 72000 = 1KHz (1ms/计数)

溢出时间公式

Tout = (ARR + 1) × (PSC + 1) / CK_PSC

CK_PSC = TIM 时钟频率(通常是 72MHz)
所以: Tout = (ARR + 1) × (PSC + 1) / 72,000,000
ARR PSC 定时时长 分辨率
999 7199 1000×7200/72M = 100ms 0.1ms
9999 7199 10000×7200/72M = 1s 0.1ms
999 71999 1000×72000/72M = 1s 1ms
65535 65535 65536×65536/72M = ~59.6s 0.91μs

参考:参考手册 §14(通用定时器寄存器描述)

CR1 寄存器关键位

名称 功能说明
0 CEN 计数器使能(0=停止,1=启动)
4 DIR 计数方向(0=向上计数,1=向下计数) 仅 TIM1/8 等可配
7 ARPE Auto-Reload Preload Enable(0=ARR立即更新,1=ARR在下次更新事件时更新)
5 OPM 单脉冲模式(1=计数到 0 后自动停止)

实验:SysTick/TIM 定时控制 LED 闪烁

软件设计(SysTick 中断版)

项目路径stm32_base/16_systick_led_twinkle_register

文件:stm32_base/16_systick_led_twinkle_register/Hardware/SysTick/systick.c

#include "systick.h"

// SysTick 初始化:产生 1ms 周期中断
void Systick_Init(void)
{
    // 选择 AHB/8 = 9MHz(更准确,且与 FreeRTOS 常用配置兼容)
    // 1ms 中断: LOAD = 9MHz / 1000 - 1 = 8999
    // 使用 AHB/8 可避免 SysTick 频率过高导致频繁中断

    // CLKSOURCE=0 (AHB/8=9MHz), TICKINT=1, ENABLE=1
    SysTick->LOAD = 9000 - 1;             // 9MHz/1000 = 9K, 9000-1
    SysTick->VAL  = 0;                    // 清当前值
    SysTick->CTRL = SysTick_CTRL_TICKINT_Msk  // |= TICKINT=1
                  | SysTick_CTRL_ENABLE_Msk;  // |= ENABLE=1
    // 注意: CLKSOURCE 默认为 0(上电复位值)
}

// SysTick 中断服务函数(由启动文件的中断向量表引用)
void SysTick_Handler(void)
{
    static uint16_t tick_count = 0;

    tick_count++;
    if (tick_count >= 1000)               // 1000 × 1ms = 1s
    {
        LED_Toggle(LED_1);               // 翻转 LED(每秒闪烁一次)
        tick_count = 0;
    }
}

软件设计(TIM 中断版)

项目路径stm32_base/18_tim_led_twinkle_register

文件:stm32_base/18_tim_led_twinkle_register/Hardware/TIM5/tim5.c

#include "tim5.h"

// TIM5 初始化:1s 溢出一次,产生更新中断
void TIM5_Init(void)
{
    /* ========== 1. 开启 TIM5 时钟 ========== */
    // TIM5 挂载在 APB1 总线上(APB1 时钟 = 36MHz)
    // 但若 APB1 分频≠1,则定时器时钟 = APB1 × 2 = 72MHz
    RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;     // |=: TIM5时钟使能

    /* ========== 2. 配置 PSC 和 ARR ========== */
    // 目标: Tout = 1s
    // CK_PSC = 72MHz, PSC = 7200 - 1 → CK_CNT = 72M/7200 = 10KHz
    // ARR = 10000 - 1 → Tout = 10000 / 10KHz = 1s
    TIM5->PSC = 7200 - 1;                   // 预分频: 72MHz → 10KHz
    TIM5->ARR = 10000 - 1;                  // 自动重装载值

    /* ========== 3. 清计数器 ========== */
    TIM5->CNT = 0;                           // 从 0 开始计数

    /* ========== 4. 使能更新中断 ========== */
    // UIE = 1: 允许更新事件产生中断
    TIM5->DIER |= TIM_DIER_UIE;             // |=: UIE位置1

    /* ========== 5. 使能 NVIC 中断 ========== */
    // TIM5 的中断号 = TIM5_IRQn
    NVIC_EnableIRQ(TIM5_IRQn);              // 在 NVIC 中使能 TIM5 中断

    /* ========== 6. 启动定时器 ========== */
    // CEN=1: 计数器开始工作
    TIM5->CR1 |= TIM_CR1_CEN;               // |=: CEN位置1
}

// TIM5 中断服务函数
void TIM5_IRQHandler(void)
{
    // 检查 SR.UIF(更新中断标志位)
    // UIF=1 表示计数器从 ARR 溢出回 0,产生了更新事件
    if (TIM5->SR & TIM_SR_UIF)              // &测试: UIF位=1?
    {
        LED_Toggle(LED_1);                  // 翻转 LED

        // 清除 UIF 标志(必须软件清除,否则持续进中断)
        // 注意: SR 寄存器是"写 0 清除"
        TIM5->SR &= ~TIM_SR_UIF;            // &=~: UIF位清0
    }
}

SysTick vs TIM 对比

对比项 SysTick TIM 通用定时器
属于 Cortex-M3 内核 芯片厂家外设
位数 24 位 16 位
预分频 固定 AHB 或 AHB/8 可编程 PSC (0~65535)
多通道 4 通道(捕获/比较/PWM)
中断 仅更新中断 更新 + 多通道捕获比较中断
精度 一般
用途 时基、裸机延时、RTOS 心跳 PWM、捕获、精确计时
数量 1 个(全芯片唯一) 7 个(多定时器可并行)

核心速查表

操作 SysTick TIM 通用定时器
开启时钟 无需开启(内核自带) `RCC->APB1ENR
配置预分频 无(AHB 或 AHB/8 固定) TIMx->PSC = value
配置重装载 SysTick->LOAD = value TIMx->ARR = value
计数方向 只减 可增可减
使能中断 `CTRL = TICKINT`
启动 `CTRL = ENABLE`
停止 CTRL &= ~ENABLE CR1 &= ~CEN
清中断标志 自动(读 CTRL) SR &= ~UIF(写 0)
当前计数值 VAL CNT

常见问题与避坑

  1. SysTick 中断不触发 → 检查 CTRL.TICKINT 是否置 1、NVIC 优先级配置(SysTick 属于异常,优先级在 SHPR3 寄存器)
  2. TIM 定时时间不对 → 检查 APB1 分频系数:如果 APB1 预分频 ≠1,则 TIM 时钟 = APB1×2(不要直接按 APB1 频率算 CK_PSC)
  3. 中断只进一次 → ISR 中忘了清除 SR.UIF 标志(SR 是写 0 清除)
  4. 修改 ARR 后定时时间不变 → CR1.ARPE=0(ARR 立即更新)或 ARPE=1(等待下次更新事件)
  5. TIM 与 SysTick 中断冲突 → 注意优先级配置,SysTick 常用最低优先级
  6. Delay_us 最大延时限制 → LOAD 最大 0xFFFFFF(16,777,215),72MHz 下单次最大 ≈ 233ms。要更长时间需循环累积