跳到主要内容

定时器笔记

定时器笔记

适用平台:STM32F1xx 系列(其他系列可参考,寄存器略有差异)
参考资料:STM32 参考手册(RM0008)、HAL/标准外设库文档


目录


3.8.1 STM32 的 5 种定时器简介

STM32 集成了多种定时器,按功能和复杂程度可分为以下 5 类:

类型代表外设位数主要特性
高级控制定时器TIM1、TIM816 位互补 PWM 输出、死区控制、刹车功能,适用于电机控制
通用定时器TIM2–TIM516 位输入捕获、输出比较、PWM、编码器接口
基本定时器TIM6、TIM716 位仅计时/计数,常用于触发 DAC
SysTick 定时器SysTick24 位Cortex-M 内核定时器,常用于 RTOS 心跳和精确延时
看门狗定时器IWDG、WWDG独立看门狗 / 窗口看门狗,防止程序跑飞

关键区别速记

  • 高级定时器 = 通用定时器 + 互补输出 + 刹车
  • 基本定时器 = 高级/通用定时器的纯计时子集
  • SysTick 属于 Cortex-M 内核,不占用 APB 总线资源

3.8.2–3.8.5 SYSTICK 定时器详解

3.8.2 SysTick 概述与寄存器

SysTick 是 Cortex-M3/M4 内核内置的 24 位递减计数器,与芯片厂商无关,所有 Cortex-M 系列通用。

核心寄存器(地址基于 0xE000E000):

寄存器偏移说明
CTRL0x10控制和状态寄存器
LOAD0x14重装载值寄存器(24 位)
VAL0x18当前计数值寄存器
CALIB0x1C校准值寄存器(只读)

CTRL 寄存器关键位:

Bit 16 - COUNTFLAG : 计数到 0 时置 1,读后自动清零
Bit 2 - CLKSOURCE : 0 = AHB/8 时钟,1 = AHB 时钟(处理器时钟)
Bit 1 - TICKINT : 1 = 使能 SysTick 异常请求
Bit 0 - ENABLE : 1 = 使能计数器

3.8.3 SysTick 工作原理

工作流程:

LOAD 寄存器写入重装载值

计数器从 LOAD 值开始递减

计数到 0 → 触发 SysTick 中断(若 TICKINT=1)

自动从 LOAD 重装,循环计数

计算定时周期:

  • 公式:T_tick = (LOAD + 1) / f_clk
  • 例:系统时钟 72 MHz,LOAD = 71999,则:
    • T = 72000 / (72 × 10^6) = 1 ms

3.8.4 SysTick 延时函数实现

轮询方式(不使用中断):

/**
* @brief 微秒级延时(基于 SysTick 轮询)
* @param us: 延时微秒数
* @note 系统时钟 72MHz,使用 AHB/8 = 9MHz 时钟源
*/
void delay_us(uint32_t us)
{
SysTick->LOAD = 9 * us - 1; // 9 ticks = 1us(9MHz 时钟)
SysTick->VAL = 0; // 清零当前值
SysTick->CTRL = 0x01; // 使能,使用 AHB/8,关中断
while (!(SysTick->CTRL & (1<<16))); // 等待 COUNTFLAG
SysTick->CTRL = 0; // 关闭定时器
}

void delay_ms(uint32_t ms)
{
while (ms--)
delay_us(1000);
}

中断方式(常用于 RTOS 或系统心跳):

volatile uint32_t g_tick_ms = 0;

/* 在 SysTick_Handler 中调用 */
void SysTick_Handler(void)
{
g_tick_ms++;
// HAL_IncTick(); // 若使用 HAL 库
}

/* 初始化:1ms 中断 */
void SysTick_Init(void)
{
SysTick_Config(SystemCoreClock / 1000); // CMSIS 标准函数
}

3.8.5 SysTick 注意事项与常见问题

问题原因解决方案
延时不准时钟源选错确认 CLKSOURCE 位与计算公式匹配
中断未触发TICKINT 未置位检查 SysTick_Config() 返回值
长时间延时溢出24 位计数器最大 16M ticks拆分为多次小延时,或改用通用定时器
RTOS 冲突FreeRTOS 也使用 SysTickFreeRTOS 会接管 SysTick,不可重复初始化

⚠️ 注意SysTick->LOAD 最大值为 0xFFFFFF(16,777,215),使用 72MHz 时钟时最大延时约 233ms,超出需分段。


3.8.6 STM32 的定时器学习要点

学习 STM32 定时器的核心路径:

时钟树理解

定时器时基配置(预分频 PSC + 自动重装 ARR)

工作模式选择(向上/向下/中央对齐计数)

功能扩展(输入捕获 / 输出比较 / PWM / 编码器)

中断配置与 NVIC

DMA 联动(高级用法)

最关键的两个参数:

  • 公式:f_timer = (f_APB × 2) / (PSC + 1)(APB 倍频后)
  • 公式:T_overflow = (ARR + 1) / f_timer

常见时钟配置(72MHz 系统时钟):

APB1 总线定时器时钟典型外设
36 MHz72 MHz(×2)TIM2–7
APB2 总线72 MHz(×1)TIM1、TIM8

3.8.7–3.8.10 定时器的数据手册

3.8.7 时基单元寄存器

通用/高级定时器共用的时基寄存器:

寄存器名称说明
TIMx_CR1控制寄存器 1使能、计数方向、对齐模式
TIMx_CR2控制寄存器 2主从模式触发源
TIMx_SMCR从模式控制外部触发、级联
TIMx_DIERDMA/中断使能各中断/DMA 请求使能位
TIMx_SR状态寄存器中断标志位
TIMx_EGR事件生成寄存器软件触发更新事件
TIMx_PSC预分频寄存器分频系数(实际分频 = PSC+1)
TIMx_ARR自动重装寄存器计数上限(产生溢出)
TIMx_CNT计数器当前计数值(可读写)

3.8.8 捕获/比较寄存器

寄存器说明
TIMx_CCMRx捕获/比较模式寄存器(配置通道工作模式)
TIMx_CCER捕获/比较使能寄存器(极性、使能)
TIMx_CCRx捕获/比较值寄存器(PWM 占空比设置)

TIMx_CCMR1 输出比较模式(OCxM 位):

000 - 冻结(不改变输出)
001 - 匹配时置有效电平
010 - 匹配时置无效电平
011 - 翻转
100 - 强制无效
101 - 强制有效
110 - PWM 模式 1(CNT<CCR 时有效)
111 - PWM 模式 2(CNT<CCR 时无效)

3.8.9 中断与 DMA 相关寄存器

TIMx_DIER 关键位:

TIM_DIER_UIE   // 更新中断使能(溢出中断)
TIM_DIER_CC1IE // 捕获/比较通道 1 中断使能
TIM_DIER_UDE // 更新 DMA 请求使能

中断服务函数框架(标准库):

void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
// 用户定时任务代码
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 必须清标志!
}
}

3.8.10 高级定时器专有寄存器(TIM1/TIM8)

寄存器说明
TIMx_BDTR刹车和死区寄存器(死区时间、刹车极性)
TIMx_RCR重复计数器(N 次溢出才触发更新事件)

死区时间配置(BDTR.DTG 字段):

  • 公式:DT = DTG[7:0] × t_DT
  • 其中 t_DT 根据 DTG[7:6] 的值由不同基准时钟决定,详见 RM0008 第 14 章。

3.8.11–3.8.12 定时器例程分析和编程实践

3.8.11 通用定时器定时中断实现(TIM2)

初始化步骤:

void TIM2_Init(uint16_t arr, uint16_t psc)
{
TIM_TimeBaseInitTypeDef TIM_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;

/* 1. 开启时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

/* 2. 配置时基 */
TIM_InitStruct.TIM_Period = arr; // 自动重装值
TIM_InitStruct.TIM_Prescaler = psc; // 预分频值
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_InitStruct);

/* 3. 使能更新中断 */
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

/* 4. 配置 NVIC */
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);

/* 5. 启动定时器 */
TIM_Cmd(TIM2, ENABLE);
}

调用示例(1ms 定时,72MHz 时钟):

// TIM2 时钟 = 72MHz
// 定时 = (arr+1)*(psc+1) / 72MHz = 1000*72/72MHz = 1ms
TIM2_Init(999, 71);

3.8.12 输入捕获实践(频率/脉宽测量)

void TIM2_IC_Init(void)
{
TIM_ICInitTypeDef TIM_ICStruct;
GPIO_InitTypeDef GPIO_InitStruct;

/* PA0 复用为 TIM2_CH1 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);

/* 输入捕获配置 */
TIM_ICStruct.TIM_Channel = TIM_Channel_1;
TIM_ICStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; // 上升沿捕获
TIM_ICStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICStruct.TIM_ICFilter = 0x00;
TIM_ICInit(TIM2, &TIM_ICStruct);

TIM_Cmd(TIM2, ENABLE);
}

/* 在中断中读取捕获值计算频率 */
uint32_t capture1 = 0, capture2 = 0;
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET)
{
static uint8_t edge = 0;
if (edge == 0) {
capture1 = TIM_GetCapture1(TIM2);
edge = 1;
} else {
capture2 = TIM_GetCapture1(TIM2);
uint32_t period = capture2 - capture1; // 单位:timer ticks
// 频率 = f_timer / period
edge = 0;
}
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
}
}

3.8.13 问题解决及 PWM 输出功能引入

常见问题与解决

现象可能原因排查方法
中断不触发忘记清标志位 / NVIC 未使能检查 ClearITPendingBit 调用位置
定时时间不准PSC/ARR 计算错误重新核算公式,用示波器验证
定时器启动后立即触发一次中断初始化时产生了更新事件初始化后调用 TIM_ClearFlag(TIMx, TIM_FLAG_Update)
重复计数器 RCR 不生效仅 TIM1/TIM8 有此功能通用定时器无 RCR

PWM 功能引入

PWM(脉冲宽度调制)本质是 输出比较功能的特殊模式,通过控制 CCRARR 的比值来改变占空比:

  • 占空比 = (CCR / (ARR + 1)) × 100%
  • f_PWM = f_timer / ((PSC+1)(ARR+1))

3.8.14–3.8.15 TIM2 的 PWM 输出功能详解

3.8.14 PWM 模式原理

PWM 模式 1(向上计数):

计数值 CNT:  0 ──────────────── ARR → 0(重装)
↑ ↑
输出: 有效 无效
|←── CCR ──→|← ARR-CCR →|
  • CNT < CCR:输出有效电平(高或低,取决于 CCER 中的极性设置)
  • CNT ≥ CCR:输出无效电平

关键寄存器配置路径:

TIMx_CR1  → 计数方向、自动重装预装载
TIMx_PSC → 预分频
TIMx_ARR → PWM 周期控制
TIMx_CCMR → OCxM=110(PWM模式1)+ OCxPE=1(预装载)
TIMx_CCER → CCxE=1(通道使能)
TIMx_CCRx → 占空比控制

3.8.15 PWM 输出引脚映射

以 TIM2 为例(STM32F103):

通道默认引脚重映射引脚
CH1PA0PA15(完全重映射)
CH2PA1PB3(完全重映射)
CH3PA2PB10(完全重映射)
CH4PA3PB11(完全重映射)

⚠️ 使用重映射时需开启 AFIO 时钟并调用 GPIO_PinRemapConfig()


3.8.16–3.8.17 TIM2 的专用 PWM 输出编程实践

3.8.16 PWM 初始化代码

/**
* @brief TIM2 CH1 PWM 输出初始化
* @param arr: 自动重装值(决定 PWM 频率)
* @param psc: 预分频值
* @note PWM 引脚:PA0
*/
void TIM2_PWM_Init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_InitStruct;
TIM_OCInitTypeDef TIM_OCStruct;

/* 1. 开启时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

/* 2. GPIO 配置为复用推挽输出 */
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);

/* 3. 时基配置 */
TIM_InitStruct.TIM_Period = arr;
TIM_InitStruct.TIM_Prescaler = psc;
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_InitStruct);

/* 4. 输出比较配置(PWM 模式 1)*/
TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCStruct.TIM_Pulse = 0; // 初始占空比 0
TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_High; // 高电平有效
TIM_OC1Init(TIM2, &TIM_OCStruct);

/* 5. 使能预装载 + 启动 */
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM2, ENABLE);
TIM_Cmd(TIM2, ENABLE);
}

3.8.17 PWM 占空比动态调节(LED 呼吸灯示例)

int main(void)
{
uint16_t duty = 0;
int8_t dir = 1;

// PWM 频率 = 72MHz / (72 * 1000) = 1kHz
TIM2_PWM_Init(999, 71);

while (1)
{
/* 动态修改 CCR1 寄存器调节占空比 */
TIM_SetCompare1(TIM2, duty);

duty += dir * 10;
if (duty >= 999) dir = -1;
if (duty == 0) dir = 1;

delay_ms(10);
}
}

占空比计算:

duty = 0    → 占空比 0%(LED 熄灭)
duty = 500 → 占空比 50%(半亮)
duty = 999 → 占空比 ~100%(最亮)

3.8.18–3.8.20 DS18B20 程序在 STM32 上的移植

3.8.18 DS18B20 移植前分析

DS18B20 是单总线(1-Wire)数字温度传感器,通讯依赖严格的 微秒级时序

从 51 单片机到 STM32 的主要差异:

项目51 单片机STM32
延时函数_nop_() / 软件循环SysTick / TIM 定时
GPIO 操作P1 = 0x01 直接赋值GPIO_SetBits() / 寄存器操作
IO 方向切换51 准双向口自动处理需手动切换 GPIO_Mode_Out_PPGPIO_Mode_IN_FLOATING
头文件reg51.hstm32f10x.h

DS18B20 关键时序要求(必须精确):

复位脉冲:总线拉低 480–960μs,释放后等待 15–60μs 检测存在脉冲
写 0:拉低 60–120μs
写 1:拉低 1–15μs,释放保持 60μs
读位:拉低 ≥1μs,释放后 15μs 内采样

3.8.19 STM32 上的 DS18B20 驱动移植

GPIO 方向切换函数:

#define DS18B20_PORT  GPIOA
#define DS18B20_PIN GPIO_Pin_1

/* 设置为输出模式 */
void DS18B20_Mode_Out(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = DS18B20_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DS18B20_PORT, &GPIO_InitStruct);
}

/* 设置为输入模式 */
void DS18B20_Mode_In(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = DS18B20_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DS18B20_PORT, &GPIO_InitStruct);
}

#define DS18B20_DQ_LOW() GPIO_ResetBits(DS18B20_PORT, DS18B20_PIN)
#define DS18B20_DQ_HIGH() GPIO_SetBits(DS18B20_PORT, DS18B20_PIN)
#define DS18B20_DQ_READ() GPIO_ReadInputDataBit(DS18B20_PORT, DS18B20_PIN)

复位与应答检测:

/**
* @brief DS18B20 复位,检测存在脉冲
* @return 0: 传感器存在,1: 未检测到
*/
uint8_t DS18B20_Reset(void)
{
uint8_t presence;

DS18B20_Mode_Out();
DS18B20_DQ_LOW();
delay_us(480); // 复位脉冲 ≥480μs
DS18B20_DQ_HIGH();
delay_us(60); // 等待存在脉冲

DS18B20_Mode_In();
presence = DS18B20_DQ_READ(); // 低电平=存在
delay_us(240); // 等待总线释放

return presence; // 0=正常
}

3.8.20 温度读取完整实现与调试

写字节与读字节:

void DS18B20_WriteByte(uint8_t byte)
{
DS18B20_Mode_Out();
for (uint8_t i = 0; i < 8; i++)
{
DS18B20_DQ_LOW();
delay_us(2);
if (byte & 0x01)
DS18B20_DQ_HIGH(); // 写 1
delay_us(60);
DS18B20_DQ_HIGH();
byte >>= 1;
}
}

uint8_t DS18B20_ReadByte(void)
{
uint8_t byte = 0;
for (uint8_t i = 0; i < 8; i++)
{
DS18B20_Mode_Out();
DS18B20_DQ_LOW();
delay_us(2);
DS18B20_Mode_In();
delay_us(10);
byte >>= 1;
if (DS18B20_DQ_READ())
byte |= 0x80;
delay_us(50);
}
return byte;
}

温度读取主函数:

/**
* @brief 读取 DS18B20 温度(单位:0.0625°C/LSB)
* @return 实际温度 = 返回值 * 0.0625
*/
float DS18B20_ReadTemp(void)
{
uint8_t temp_l, temp_h;
int16_t raw;
float temperature;

DS18B20_Reset();
DS18B20_WriteByte(0xCC); // Skip ROM(单个传感器)
DS18B20_WriteByte(0x44); // 启动温度转换

delay_ms(750); // 12 位精度转换时间 750ms

DS18B20_Reset();
DS18B20_WriteByte(0xCC);
DS18B20_WriteByte(0xBE); // 读暂存器

temp_l = DS18B20_ReadByte(); // 低字节
temp_h = DS18B20_ReadByte(); // 高字节

raw = (temp_h << 8) | temp_l;
temperature = raw * 0.0625f;

return temperature;
}

移植常见问题:

问题原因解决
复位无应答上拉电阻缺失或阻值错误确认 4.7kΩ 上拉至 VCC
读取恒为 85°C上电默认值,未真正完成转换等待 750ms 转换时间
读取值偏差大延时不准确,时序错误用逻辑分析仪验证 1-Wire 时序
IO 方向切换慢使用库函数切换耗时改用直接寄存器操作 GPIOA->CRL

附录

常用定时器配置速查

/* 1ms 定时(72MHz)*/
TIM_Init(999, 71); // PSC=71, ARR=999 → T = 1ms

/* 10kHz PWM(72MHz)*/
TIM_PWM_Init(719, 9); // PSC=9, ARR=719 → f=10kHz

/* 1s 定时(72MHz)*/
TIM_Init(9999, 7199); // PSC=7199, ARR=9999 → T=1s

定时器资源占用建议

用途推荐定时器
系统滴答 / 延时SysTick
普通定时中断TIM6、TIM7
PWM 输出TIM2–TIM5
电机控制(互补 PWM)TIM1、TIM8
输入捕获(测频/测宽)TIM2–TIM5
看门狗IWDG(独立)/ WWDG(窗口)

笔记版本:v1.0 | 平台:STM32F103 | 库:标准外设库(SPL)