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/User/systick.h
#ifndef __SYSTICK_H
#define __SYSTICK_H
#include "stm32f10x.h"
#include "led.h"
void Systick_Init(void);
#endif
文件:stm32_base/16_systick_led_twinkle_register/User/systick.c
#include "systick.h"
void Systick_Init(void)
{
// 1. 设置初始装载的值,1ms 产生一次中断,72000 时钟周期
SysTick->LOAD = 72000 - 1;
// 2. 设置时钟源
SysTick->CTRL |= SysTick_CTRL_CLKSOURCE;
// 3. 使能中断
SysTick->CTRL |= SysTick_CTRL_TICKINT;
// 4. 启动定时器
SysTick->CTRL |= SysTick_CTRL_ENABLE;
}
// 全局变量作为中断计数
uint16_t count = 0;
void SysTick_Handler(void)
{
count++;
// 计数到 1000 次中断,即是1s,翻转LED1
if (count == 1000)
{
LED_Toggle(LED_1);
count = 0;
}
}
项目路径:stm32_base/18_tim_led_twinkle_register
文件:stm32_base/18_tim_led_twinkle_register/Hardware/TIM/tim6.h
#ifndef __TIM6_H
#define __TIM6_H
#include "stm32f10x.h"
#include "led.h"
void TIM6_Init(void);
#endif
文件:stm32_base/18_tim_led_twinkle_register/Hardware/TIM/tim6.c
#include "tim6.h"
void TIM6_Init(void)
{
// 1. 使能定时器
RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
// 2. 设置预分频值 7199,表示7200分频得到10000Hz(100us)
TIM6->PSC = 7199;
// 3. 设置自动装载值 99,表示 100 次计数得到 1s
TIM6->ARR = 99;
// 4. 打开更新中断使能
TIM6->DIER |= TIM_DIER_UIE;
// 5. NVIC配置
NVIC_SetPriorityGrouping(3);
NVIC_SetPriority(TIM6_IRQn, 2);
NVIC_EnableIRQ(TIM6_IRQn);
// 6. 使能计数器
TIM6->CR1 = TIM_CR1_CEN;
}
// 中断服务函数
void TIM6_IRQHandler(void)
{
// 清除中断标志位
TIM6->SR &= ~TIM_SR_UIF;
// 每1s翻转一次LED2
LED_Toggle(LED_2);
}
文件:stm32_base/18_tim_led_twinkle_register/User/main.c
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "tim6.h"
int main(void)
{
// 初始化
USART_Init();
LED_Init();
TIM6_Init();
printf("Hello world!\n");
while (1)
{
}
}
HAL 库使用 TIM_HandleTypeDef 管理定时器,CubeMX 生成初始化代码后通过宏或 API 控制。
// CubeMX 生成: tim.c 中的 MX_TIMx_Init()
TIM_HandleTypeDef htim5;
void MX_TIM5_Init(void)
{
htim5.Instance = TIM5;
htim5.Init.Prescaler = 7200 - 1; // PSC
htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
htim5.Init.Period = 10000 - 1; // ARR
htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim5.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
HAL_TIM_Base_Init(&htim5);
}
// HAL 库启动定时器中断
HAL_TIM_Base_Start_IT(&htim5);
// 不使用中断: HAL_TIM_Base_Start(&htim5);
// HAL 库定时器中断回调(用户重写此函数)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM5)
{
static uint16_t tick = 0;
if (++tick >= 1000) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0); tick = 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 |