tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(进阶篇)V1.0.1 — 第1章:定时器 + 配套代码16~19" author: "尚硅谷研究院" date: 2026-07-15
用生活理解:SysTick 就像厨房里的倒计时秒表——设好时间(LOAD 值),它倒数到 0 就"叮"一声(中断)。TIM 通用定时器就像高级可编程定时器——可以预分频(时间压缩/扩展)、自动重装(倒计时到 0 自动重新开始)、还能输出 PWM 或测量外部脉冲。SysTick 是内核自带的"简单秒表",TIM 是片上外设的"多功能定时器"。
SysTick 是 Cortex-M3 内核自带的 24 位递减计数器,属于内核外设,与具体芯片厂商无关。在所有 Cortex-M3 产品间处理方式相同。
参考:Cortex-M3 权威指南 §8(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。
| 用法 | 配置 | 特点 |
|---|---|---|
| 查询延时 | CLKSOURCE=AHB, TICKINT=0 | 轮询 COUNTFLAG 标志,阻塞式 |
| 周期性中断 | CLKSOURCE=AHB/8, TICKINT=1 | 产生 1ms 定时中断(FreeRTOS 时基) |
// 微秒级延时(基于 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);
}
}
STM32F103ZET6 有 7 个定时器:
| 定时器 | 类型 | 位数 | 主要特性 |
|---|---|---|---|
| TIM1 | 高级 | 16 | 带互补 PWM 输出、死区插入、刹车功能(电机控制) |
| TIM2~5 | 通用 | 16 | 时基、PWM、捕获/比较(最常用) |
| TIM6~7 | 基本 | 16 | 仅时基功能,无外部 I/O |
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)
其中:
示例(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(通用定时器寄存器描述)
| 位 | 名称 | 功能说明 |
|---|---|---|
| 0 | CEN | 计数器使能(0=停止,1=启动) |
| 4 | DIR | 计数方向(0=向上计数,1=向下计数) 仅 TIM1/8 等可配 |
| 7 | ARPE | Auto-Reload Preload Enable(0=ARR立即更新,1=ARR在下次更新事件时更新) |
| 5 | OPM | 单脉冲模式(1=计数到 0 后自动停止) |
项目路径: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;
}
}
项目路径: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 | 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 |