STM32(M4)入门:定时器延时与系统滴答(价值 3w + 的嵌入式开发指南)
第 1 章 延时:嵌入式系统的时间控制基石
1.1 延时基础:从概念到硬件实现
1.1.1 什么是延时?
定义:延时是通过软件或硬件手段,使程序执行过程中暂停指定时间,再继续后续操作的技术。本质是对时间的精确或粗略控制,确保硬件时序、任务调度或通信协议的正确执行。
核心作用:让程序在特定时间点或时间段内等待,满足外设响应、任务协调、时序匹配等需求(如按键消抖、LED 闪烁、传感器初始化等待)。
1.1.2 为什么需要延时?
- 硬件稳定需求:
- 按键按下后需等待 5-20ms 消除机械抖动,避免误触发。
- 外设上电后需等待初始化完成(如 LCD 屏幕背光稳定)。
- 周期性任务控制:
- 实现 LED 周期性闪烁(如 1 秒亮灭一次)。
- 定时采集传感器数据(如每 10ms 读取一次温湿度)。
- 通信时序匹配:
- I2C/SPI 通信中需严格遵循时钟周期和信号保持时间。
- UART 通信中波特率的精确时钟控制。
1.1.3 如何实现延时?两种核心方案
方案 1:软件延时(CPU 空转)
// 简单毫秒级延时(依赖CPU主频,STM32F4 168MHz时,每循环约1μs)
void delay_ms(uint32_t ms) { for (; ms > 0; ms--) { for (uint32_t i = 0; i < 168000; i++); // 空循环消耗时间 }
}
- 优点:实现简单,无需硬件配置。
- 缺点:
- 阻塞 CPU:延时期间无法处理中断或其他任务,导致系统卡顿。
- 精度低:受 CPU 主频影响,超频 / 降频时需重新校准。
- 资源浪费:长延时占用大量 CPU 资源,效率低下。
方案 2:硬件定时器延时(推荐)
核心原理:利用 STM32 内部定时器的计数功能,通过配置固定频率的时钟源和分频器,实现高精度、非阻塞式延时。
优势:
- 高精度:误差仅由时钟源稳定性决定(如 84MHz 晶振误差 < 0.1%)。
- 释放 CPU:延时期间 CPU 可执行其他任务(如处理串口数据、刷新屏幕)。
- 灵活扩展:通过调整分频系数和计数值,支持 μs/ms 级长延时。
1.2 STM32 定时器体系:分类与核心原理
1.2.1 定时器分类与选型
STM32F4 系列包含 3 类 15 个定时器,按功能划分为:
类型 | 功能特点 | 包含型号 | 延时相关特性 |
---|---|---|---|
基本定时器 | 16 位,仅支持定时计数和 DAC 触发(STM32F401 无此功能) | TIM6、TIM7 | 配置简单,适合纯延时场景(如 LED 闪烁) |
通用定时器 | 16/32 位,支持输入捕获、输出比较、PWM 生成(4 个独立通道) | TIM2~TIM5、TIM9~TIM11 | 可实现复杂时序控制(如 PWM 调光) |
高级定时器 | 16 位,含互补 PWM 输出、死区控制、刹车功能(电机控制专用) | TIM1、TIM8 | 支持电机驱动的精准时序控制 |
延时场景选型:
- 基础延时:选择基本定时器(TIM7),因其功能简洁、资源占用少,只需配置定时计数功能即可满足需求。
- 复杂场景:通用 / 高级定时器(如 TIM3 实现 PWM 呼吸灯,TIM1 控制电机转速)。
1.2.2 什么是定时器?
本质:定时器是一个带时钟的计数器,通过对固定频率的时钟信号计数,将计数值转换为时间间隔。
核心组件:
- 时钟源:
- 定时器 7(TIM7)的时钟源来自 APB1 总线,系统默认配置下:
- 系统主频 168MHz,APB1 预分频 4→APB1 频率 42MHz。
- 定时器自动倍频 ×2→84MHz 时钟源(关键:APB 预分频≠1 时,定时器时钟自动翻倍)。
- 定时器 7(TIM7)的时钟源来自 APB1 总线,系统默认配置下:
- 预分频器(PSC):
- 16 位寄存器,将 84MHz 时钟分频为更低频率(1~65536 分频)。
- 例:PSC=8399(8400 分频)→ 计数频率 = 84MHz/8400=10kHz,单次计数周期 0.1ms。
- 计数器(CNT):
- 16 位向上计数器,从 0 开始递增,到达自动重装载值(ARR)时溢出,触发更新事件。
- 自动重装载寄存器(ARR):
- 设置计数器溢出阈值,决定单次定时周期(如 ARR=9 对应 10 次计数,即 1ms)。
关键公式:
计数周期(Tclk)= 1 / 时钟源频率 = 1/84MHz ≈ 0.0119μs
分频后周期(Tpre)= Tclk × (PSC + 1) // 分频值=PSC+1
延时时间(Tdelay)= Tpre × (ARR + 1) // 计数值=ARR+1(从0开始计数)
1.2.3 定时器如何实现延时?
- 设定目标延时:例如需要 1ms 延时。
- 计算参数:
- 选择分频系数 8400(PSC=8399),得到计数周期 0.1ms。
- 计算 ARR:1ms / 0.1ms / 次 = 10 次计数 → ARR=10-1=9(计数器从 0 开始)。
- 启动计数:计数器从 0 递增,到达 ARR=9 时溢出,触发延时完成标志。
- 检测溢出:通过标志位判断延时是否到达,清除标志位后可重复使用。
1.3 定时器 7 深度解析:从时钟源到计数逻辑
1.3.1 时钟源定位:揭秘 TIM7 的 “时间脉搏”
时钟源追踪:从系统时钟到定时器时钟
STM32 的时钟体系如同精密齿轮组,TIM7 的时钟源需从 RCC 时钟树逐层定位:
- 系统时钟(SYSCLK):作为整个系统的核心,默认频率 168MHz,为 AHB 总线、内核及存储器提供时钟。
- AHB 总线分频:系统时钟经 AHB 预分频器(默认 1 分频)直接驱动 AHB 总线,频率保持 168MHz。
- APB1 总线分频:AHB 总线信号经 APB1 预分频器(文档中配置为 4 分频),得到 APB1 总线频率:
APB1频率 = 系统时钟 / 预分频值 = 168MHz / 4 = 42MHz
- 定时器时钟倍频:STM32 硬件特性规定,当 APB 预分频值≠1 时,定时器时钟自动倍频 ×2(提升精度):
TIM7时钟源 = APB1频率 × 2 = 42MHz × 2 = 84MHz
关键结论:TIM7 的时钟源最终为 84MHz,是 APB1 总线频率的 2 倍,这是理解后续计时计算的核心前提。
时钟源的作用:定义计数的 “最小时间单位”
- 84MHz 意味着时钟周期为:
时钟周期 = 1 / 84MHz ≈ 0.0119μs(即每0.0119微秒产生一个计数脉冲)
- 定时器的所有计时功能,本质是对这个高频脉冲的计数累积。
1.3.2 分频器机制:让高频时钟 “慢下来”
分频器工作原理:高频信号的 “减速器”
- 分频概念:将 84MHz 的高频时钟按固定比例降低频率,例如 84 分频后,频率降至 1MHz(84MHz/84=1MHz)。
- 实现方式:通过定时器的预分频寄存器
TIM7->PSC
设置分频系数(0~65535),实际分频值为PSC+1
:- 当
PSC=83
时,分频值 = 84,对应 84 分频。 - 当
PSC=8399
时,分频值 = 8400,对应 8400 分频。
- 当
- 核心作用:降低计数频率,使单次计数周期变长,从而适配不同延时精度需求。
分频前后对比:从 μs 级到 ms 级的灵活切换
分频配置 | 分频值 | 计数频率 | 单次计数周期 | 最大计时时间(16 位计数器) |
---|---|---|---|---|
默认配置 | 84 | 1MHz(84MHz/84) | 1μs | 65536μs=65.536ms |
加大分频 | 8400 | 10kHz(84MHz/8400) | 0.1ms | 65536×0.1ms=6.5536s |
分频系数计算公式:
计数频率 = 时钟源频率 / 分频值 = 84MHz / (PSC+1)
单次计数周期 = 1 / 计数频率 = (PSC+1) / 84MHz
示例计算:
- 当
PSC=83
(84 分频):计数频率 = 84MHz / 84 = 1MHz,单次计数周期=1μs
- 当
PSC=8399
(8400 分频):计数频率 = 84MHz / 8400 = 10kHz,单次计数周期=0.1ms
1.3.3 最大计时时间扩展:在精度与范围间找到平衡
为什么需要扩展计时范围?
- 默认 84 分频时,最大计时仅 65.536ms,无法满足 LED 闪烁(1s)、传感器采集(10ms)等常见需求。
- 通过加大分频系数,可显著延长计时范围,例如 8400 分频时最大计时达 6.5536s,覆盖多数基础延时场景。
扩展方法:调整 PSC 寄存器
- 步骤 1:确定目标计数频率
- 若需要 0.1ms 的单次计数周期(10kHz 频率),则分频值 = 84MHz / 10kHz = 8400,对应
PSC=8399
。
- 若需要 0.1ms 的单次计数周期(10kHz 频率),则分频值 = 84MHz / 10kHz = 8400,对应
- 步骤 2:计算最大计时时间
最大计时时间 = 单次计数周期 × 最大计数值(65536) = 0.1ms × 65536 = 6553.6ms = 6.5536s
- 关键权衡:
- 分频系数↑:计数频率↓,单次计数周期↑,最大计时时间↑,但精度↓(如 0.1ms 精度 vs 1μs 精度)。
- 分频系数↓:计数频率↑,单次计数周期↓,最大计时时间↓,但精度↑。
应用场景匹配:
- μs 级高精度延时:选择小分频系数(如 84 分频),用于 I2C 通信的时序等待(需 μs 级精度)。
- ms 级长延时:选择大分频系数(如 8400 分频),用于 LED 闪烁、按键消抖(允许 ms 级误差)。
1.3.4 实战:计算不同分频下的计时参数
案例 1:实现 100ms 延时(8400 分频)
- 确定参数:
- 目标延时 = 100ms,单次计数周期 = 0.1ms(10kHz 频率)。
- 需计数次数 = 100ms / 0.1ms=1000 次,对应
ARR=999
(计数器从 0 开始)。
- 公式验证:
延时时间 = (ARR+1) × 单次计数周期 = 1000 × 0.1ms = 100ms
案例 2:极限最大计时(8400 分频)
- 最大计数值 = 65536,单次计数周期 = 0.1ms:
最大计时=65536 × 0.1ms=6553.6ms≈6.55s
- 若需更长延时(如 10s),需结合软件循环或中断(见后续章节)。
1.3.5 分频器配置注意事项
- 寄存器范围:
TIMx_PSC
为 16 位寄存器,分频值范围 1~65536(对应 PSC=0~65535)。
- 时钟使能:
- 配置前需通过
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);
使能 TIM7 时钟,否则分频配置无效。
- 配置前需通过
- 动态调整:
- 可在运行中修改
PSC
和ARR
寄存器,实现延时参数的动态切换(如按键调节 LED 闪烁频率)。
- 可在运行中修改
1.4 定时器 7 配置实战:三步实现精准延时
1.4.1 硬件准备
- 开发板:STM32F407ZET6(TIM7 挂载 APB1 总线)。
- 工具:Keil MDK + ST-Link。
- 系统配置:默认 168MHz 主频,APB1 预分频 4,TIM7 时钟源 84MHz。
1.4.2 配置步骤详解(以 1s 延时为例)
步骤 1:开启定时器时钟
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_tim.h" // 使能APB1总线下的TIM7时钟(必做!外设默认时钟关闭)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);
RCC_APB1Periph_TIM7
:指定操作 TIM7 的时钟控制寄存器。- 注意:未开启时钟时,定时器完全不工作。
步骤 2:初始化定时器参数
// 定义定时器初始化结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 配置1s延时参数(84MHz→8400分频→10kHz,计数值10000次)
TIM_TimeBaseStructure.TIM_Prescaler = 8399; // 预分频值(8400-1)
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式
TIM_TimeBaseStructure.TIM_Period = 9999; // 自动重装载值(10000-1)
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟不分频 // 初始化TIM7
TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStructure);
TIM_Prescaler
:预分频系数,实际分频 = 值 + 1(8399→8400 分频)。TIM_Period
:重装载值,计数器到达此值时溢出(10000 次计数对应 1s)。
步骤 3:启动定时器并检测溢出
while (1) { LED1 ^= 1; // 翻转LED状态(假设LED1连接到GPIOA5) // 启动定时器 TIM_Cmd(TIM7, ENABLE); // 等待溢出标志位(TIM_FLAG_Update)置1(轮询检测) while (TIM_GetFlagStatus(TIM7, TIM_FLAG_Update) == RESET); // 清除溢出标志(否则下次会立即触发) TIM_ClearFlag(TIM7, TIM_FLAG_Update); // 停止定时器(非必要,可保持运行以减少重复配置) TIM_Cmd(TIM7, DISABLE);
}
TIM_GetFlagStatus
:检测定时器溢出标志,返回 SET(已溢出)或 RESET(未溢出)。TIM_ClearFlag
:必须手动清除标志位,避免重复检测导致延时失效。
1.4.3 封装通用 ms 级延时函数
/** * @brief TIM7延时函数(支持ms级任意延时) * @param ms:延时毫秒值(范围:0~65535,受16位计数器限制) * @note 预分频固定为8400(8399),单次计数周期0.1ms */
void TIME7_Delay(u32 ms) { // 计算重装载值:ms × 10次计数/ms - 1(计数器从0开始) uint16_t reload = ms * 10 - 1; TIM_SetAutoreload(TIM7, reload); // 设置自动重装载值 TIM_Cmd(TIM7, ENABLE); // 启动定时器 while (TIM_GetFlagStatus(TIM7, TIM_FLAG_Update) == RESET); // 等待溢出 TIM_ClearFlag(TIM7, TIM_FLAG_Update); // 清除标志位 TIM_Cmd(TIM7, DISABLE); // 停止定时器(可选)
}
- 参数范围:因 TIM7 是 16 位计数器,最大计数值 65535,故最大延时为 65535×0.1ms=6553.5ms(约 6.55s)。
- 灵活性:通过修改
reload
值,可动态调整延时时间,无需重新初始化定时器。
1.5 常见问题与避坑指南
1.5.1 延时精度误差大?
- 原因 1:时钟源计算错误
- 未考虑 APB 预分频后的倍频机制(APB1=42MHz 时,TIM7 时钟自动 ×2→84MHz)。
- 原因 2:预分频值 / 重装载值未减 1
- 计数器从 0 开始计数,实际计数值 = 寄存器值 + 1(如 ARR=9 对应 10 次计数)。
- 解决:严格按公式计算:
ARR = (目标时间 / 计数周期) - 1
。
1.5.2 标志位不触发?
- 排查步骤:
- 是否调用
TIM_Cmd(ENABLE)
启动定时器(未启动则计数器不工作)。 - 标志位类型是否正确:延时检测用
TIM_FLAG_Update
,而非中断标志TIM_IT_Update
。 - 是否在初始化时遗漏时钟使能(RCC 配置是否正确)。
- 是否调用
1.5.3 多定时器冲突?
- 原则:不同定时器挂载于不同总线(APB1/APB2),可同时工作,但需避免高频溢出导致 CPU 负载过高。
- 优化:非关键延时用阻塞式(如 LED 闪烁),关键任务用中断式(如传感器数据采集)。
1.6 知识总结与学习路径
1.6.1 核心知识点图谱
1.6.2 实战建议
- 基础练习:
- 编写 TIM7 延时函数,实现 LED 1s 闪烁(结合 GPIO 输出)。
- 测试不同分频系数对延时的影响(如 84 分频实现 65ms 延时,8400 分频实现 6.5s 延时)。
- 进阶应用:
- 用 TIM7 实现按键消抖(延时 10ms 后检测按键电平)。
- 结合通用定时器(如 TIM3)实现 PWM 调光,理解定时器的输出比较功能。
- 调试工具:
- 使用 STM32CubeMX 图形化配置 TIM7,对比手动代码配置的差异。
- 通过串口打印延时时间,验证精度(如延时 1s 后打印 “Delay 完成”)。
通过掌握定时器 7 的原理与配置,你将建立嵌入式时间控制的核心能力,为后续学习 PWM、输入捕获等高级功能奠定基础。下一章将深入探讨系统滴答定时器(SysTick),实现更高效的 μs 级延时与系统心跳功能。
第二章:系统滴答(SysTick)—— 内核级延时与心跳定时器
2.1 系统滴答基础:Cortex-M 内核的 “心跳引擎”
2.1.1 什么是 SysTick?
定义:SysTick(系统滴答定时器)是 Cortex-M 内核集成的 24 位向下计数器,专为实时操作系统(RTOS)和精准延时设计,支持 μs/ms 级高精度定时,是 STM32 嵌入式开发的核心时间管理工具。
核心特性:
- 内核级外设:直接由 Cortex-M 内核控制,独立于 STM32 片上外设,兼容性强(所有 Cortex-M4 芯片均支持)。
- 双时钟源:可选择系统时钟(SYSCLK,168MHz)或外部时钟(HCLK/8,21MHz),平衡精度与最大计时范围。
- 低功耗模式:支持在睡眠模式下运行,适合电池供电设备。
2.1.2 为什么选择 SysTick?
对比项 | SysTick | 基本定时器(如 TIM7) |
---|---|---|
位数 | 24 位(最大计数值 16777216) | 16 位(最大计数值 65536) |
时钟源 | 系统时钟 / 外部时钟(可选) | APB1 总线倍频后时钟(固定 84MHz) |
中断支持 | 内置系统滴答中断(SysTick_IRQ) | 需要配置 NVIC 中断控制器 |
典型场景 | 精准短延时、RTOS 心跳时钟 | 长延时、周期性任务触发 |
代码复杂度 | 仅操作 3 个寄存器,简单高效 | 需要初始化结构体,配置步骤较多 |
结论:SysTick 适合μs/ms 级精准短延时和系统级时间管理(如 RTOS 任务调度),而 TIM7 更适合 ms 级长延时(见第一章)。
2.2 关键寄存器:SysTick 的 “时间控制中心”
2.2.1 控制寄存器(SysTick->CTRL)
位域 | 功能 | 操作示例 | |
---|---|---|---|
BIT0(ENABLE) | 定时器使能位:1 = 启动,0 = 停止 | `SysTick->CTRL= 1<<0;`(启动) | |
BIT1(TICKINT) | 中断使能位:1 = 允许溢出时产生中断 | `SysTick->CTRL= 1<<1;`(使能中断) | |
BIT2(CLKSOURCE) | 时钟源选择:0 = 外部时钟(HCLK/8=21MHz),1 = 系统时钟(SYSCLK=168MHz) | SysTick->CTRL &= ~(1<<2); (选择 21MHz) | |
BIT16(COUNTFLAG) | 溢出标志位:计数器从 LOAD 递减到 0 时置 1,需软件清除 | if (SysTick->CTRL & (1<<16)) { ... } (检测溢出) |
2.2.2 重装载寄存器(SysTick->LOAD)
- 作用:设置计数器初始值,决定定时周期(向下计数到 0 时溢出)。
- 范围:0~16777215(24 位),对应最大定时时间:
- 系统时钟 168MHz 时:
16777216 / 168MHz ≈ 99.86ms
- 外部时钟 21MHz 时:
16777216 / 21MHz ≈ 798.9ms
(更适合长延时)
- 系统时钟 168MHz 时:
2.2.3 当前值寄存器(SysTick->VAL)
- 作用:实时显示当前计数值(从 LOAD 递减到 0)。
- 特性:读取时返回当前值,写入任意值会立即重置计数器(常用
SysTick->VAL = 0;
清空计数)。
2.3 三步配置法:从寄存器到精准延时

2.3.1 步骤 1:选择时钟源(关键!影响精度和范围)
// 选项1:系统时钟(168MHz,精度高,适合μs级延时)
SysTick->CTRL |= (1 << 2); // CLKSOURCE=1,选择系统时钟
// 选项2:外部时钟(21MHz,计时范围更长,适合ms级延时)
SysTick->CTRL &= ~(1 << 2); // CLKSOURCE=0,选择HCLK/8=21MHz
- 时钟源计算:
- 系统时钟(SYSCLK):默认 168MHz,来自 PLL 锁相环,精度最高(误差 < 0.1%)。
- 外部时钟(HCLK/8):HCLK=AHB 总线时钟 = 168MHz,分频后 21MHz,最大计时范围扩展 8 倍。
2.3.2 步骤 2:设置重装载值(核心公式推导)
目标:实现 1ms 延时(以系统时钟 168MHz 为例)。
- 计算计数值:
计数值 = 系统时钟频率 × 目标时间 = 168MHz × 1ms = 168000
- 写入 LOAD 寄存器:
SysTick->LOAD = 168000; // 168000次计数对应1ms(168MHz时钟)
通用公式:
计数值 = 时钟源频率 × 延时时间
例如:延时t微秒 → 计数值 = 时钟源频率(MHz) × t(μs)
2.3.3 步骤 3:启动定时器并检测溢出
// 清空当前计数值(可选,确保从0开始计数)
SysTick->VAL = 0;
// 启动定时器
SysTick->CTRL |= (1 << 0);
// 等待溢出(检测COUNTFLAG位,位16)
while (!(SysTick->CTRL & (1 << 16)));
// 停止定时器(非必要,可保持运行用于连续定时)
SysTick->CTRL &= ~(1 << 0);
- 注意:COUNTFLAG 位在溢出后一直保持 1,需通过软件检测,无需手动清除(写入 VAL 寄存器会自动清除)。
2.4 封装通用延时函数:从 μs 到 ms 级全覆盖
2.4.1 系统初始化函数:SysTick_Init
#include "SysTick.h" float fck_us; // 微秒级计数参数(21MHz下为21,即1μs=21次计数)
float fck_ms; // 毫秒级计数参数(21MHz下为21000,即1ms=21000次计数) /** * @brief 系统滴答初始化(默认外部时钟21MHz) * @param CLK 系统主频(如168MHz) * @note 自动选择外部时钟源(CLK/8),计算微秒/毫秒计数参数 */
void SysTick_Init(u32 CLK) { // 选择外部时钟源(CLK/8=21MHz,当CLK=168MHz时) SysTick->CTRL = 0; // 复位控制寄存器,默认0=外部时钟源 // 计算计数参数(核心公式:计数值=时钟频率×时间) fck_us = (float)CLK / 8.0; // 1μs对应的计数值(21MHz→21次/μs) fck_ms = fck_us * 1000.0; // 1ms对应的计数值(21000次/ms)
}
- 初始化步骤:
- 清零控制寄存器,选择外部时钟源
- 根据系统主频计算计数参数(关键:21MHz=168MHz/8)
2.4.2 微秒级延时函数:delay_us
/** * @brief 微秒级延时(阻塞式) * @param n 延时时间(μs),最大约798900μs(798ms) * @note 基于外部时钟21MHz,1μs=21次计数 */
void delay_us(u32 n) { // 1. 设置重装载值(计数值=21次/μs × nμs) SysTick->LOAD = (u32)(fck_us * n); // 2. 清空计数器 SysTick->VAL = 0; // 3. 启动定时器 SysTick->CTRL |= 1 << 0; // 4. 等待溢出标志(COUNTFLAG位16置1) while (!(SysTick->CTRL & (1 << 16))); // 5. 停止定时器(可选,下次使用需重新启动) SysTick->CTRL &= ~(1 << 0);
}
2.4.3 毫秒级延时函数:delay_ms
/** * @brief 毫秒级延时(支持超长延时,自动分段处理) * @param n 延时时间(ms),最大798ms * @note 分段调用500ms子函数,避免单次计数值超过24位寄存器范围 */
void delay_ms(u32 n) { u32 remainder = n % 500; // 计算剩余时间 u32 cycles = n / 500; // 计算完整500ms周期数 // 1. 处理完整500ms周期 while (cycles--) { delay_nms(500); // 调用500ms子函数(见下方) } // 2. 处理剩余时间 if (remainder != 0) { delay_nms(remainder); }
} /** * @brief 500ms以内延时子函数(静态函数,内部调用) * @param n 延时时间(ms),n≤500 */
static void delay_nms(u32 n) { // 1. 设置重装载值(21000次/ms × n ms) SysTick->LOAD = (u32)(fck_ms * n); // 2. 清空计数器并启动 SysTick->VAL = 0; SysTick->CTRL |= 1 << 0; // 3. 等待溢出 while (!(SysTick->CTRL & (1 << 16))); // 4. 停止定时器 SysTick->CTRL &= ~(1 << 0);
}
- 分段策略:
由于 24 位寄存器最大计数值 16777216,21MHz 下最大延时约 798ms,通过分段 500ms 避免单次超限
2.5 实战案例:用 SysTick 实现 LED 呼吸灯(PWM 调光)
2.5.1 硬件连接
- LED 正极接 PA5(GPIOA5),负极接 GND,串联 220Ω 电阻。
- 通过改变 LED 亮灭时间比例(占空比)实现亮度渐变。
2.5.2 代码实现(核心逻辑)
// 定义占空比数组(0~100,代表亮度百分比)
uint8_t duty_cycle[101] = {0, 1, 2, ..., 100, 99, ..., 1, 0}; int main() { // 初始化GPIOA5为推挽输出 GPIO_InitTypeDef GPIO_InitStruct; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); while (1) { for (int i = 0; i < 101; i++) { // 点亮LED(高电平) GPIO_SetBits(GPIOA, GPIO_Pin_5); SysTick_Delay_us(duty_cycle[i] * 10); // 亮的时间 // 熄灭LED(低电平) GPIO_ResetBits(GPIOA, GPIO_Pin_5); SysTick_Delay_us((100 - duty_cycle[i]) * 10); // 灭的时间 } }
}
- 原理:通过 SysTick 精确控制 LED 亮灭时间,占空比从 0% 到 100% 再到 0% 循环,实现呼吸灯效果。
2.6 进阶应用:SysTick 作为系统心跳(RTOS 基础)
2.6.1 配置周期性中断(1ms 心跳)
// 初始化SysTick中断(1ms周期,系统时钟168MHz)
void SysTick_Init(void) { // 设置计数值:168000次计数=1ms SysTick->LOAD = 168000; // 使能中断和系统时钟 SysTick->CTRL = (1 << 2) | (1 << 1) | (1 << 0); // 配置中断优先级(可选,Cortex-M4默认优先级) NVIC_SetPriority(SysTick_IRQn, 0);
} // 中断服务函数(自动生成,需在startup文件中声明)
void SysTick_Handler(void) { static uint32_t tick = 0; tick++; // 每1ms递增,作为系统时间戳 if (tick % 1000 == 0) { LED_Toggle(); // 每秒翻转LED状态 }
}
- 应用场景:
- RTOS(如 FreeRTOS)用此中断实现任务调度(如每 1ms 切换一次任务)。
- 记录系统运行时间(通过全局变量
tick
获取 ms 级时间戳)。
2.6.2 中断与轮询对比
模式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
轮询 | 代码简单,无需中断配置 | 阻塞 CPU,无法处理其他任务 | 简单延时,无并发需求 |
中断 | 非阻塞,支持多任务处理 | 需配置 NVIC,代码较复杂 | 系统级时间管理,RTOS 任务调度 |
2.7 常见问题与避坑指南
2.7.1 延时不准确?
- 原因 1:时钟源选择错误
- 系统时钟下,计数值 = 延时时间 (μs)× 系统时钟 (MHz),如 168MHz 下 1ms 需 168000 次计数,不可直接写固定值。
- 原因 2:寄存器溢出
- 24 位计数器最大计数值 16777216,系统时钟下最大延时约 99.86ms,超过需分多次延时。
2.7.2 中断未触发?
- 排查步骤:
- 是否设置
TICKINT
位(SysTick->CTRL |= 1<<1)。 - 中断服务函数名是否正确(必须为
SysTick_Handler
,与启动文件匹配)。 - 计数值是否大于 0(LOAD=0 时定时器不会启动)。
- 是否设置
2.7.3 低功耗模式下失效?
- 解决方案:
- 在睡眠模式下,通过
SysTick->CTRL |= 1<<3;
使能睡眠模式下的定时器运行。 - 避免在停机模式(Stop Mode)下使用 SysTick,需切换到唤醒时钟源。
- 在睡眠模式下,通过
2.8 知识总结与学习路径
2.8.1 核心知识点图谱
2.8.2 学习建议
- 基础练习:
- 编写 SysTick 延时函数,实现 LED 以 100μs 间隔快速闪烁,观察示波器波形验证精度。
- 对比 SysTick 延时与 TIM7 延时的 CPU 占用率(通过串口打印空闲任务执行时间)。
- 进阶实践:
- 用 SysTick 中断实现一个简易任务调度器,每隔 50ms 执行一次 LED 翻转,每隔 100ms 打印一次日志。
- 在低功耗模式下测试 SysTick 延时,验证睡眠模式下的计时准确性。
- 调试工具:
- 使用 STM32CubeMX 的图形化配置生成 SysTick 初始化代码,对比手动编写的差异。
- 通过 Keil MDK 的寄存器视图实时监控 SysTick->VAL 的值,观察计数过程。
掌握 SysTick 后,你将拥有嵌入式系统的 “时间脉搏”,无论是精准延时还是系统级任务调度都能游刃有余。下一章我们将进入中断。
相关文章:
STM32(M4)入门:定时器延时与系统滴答(价值 3w + 的嵌入式开发指南)
第 1 章 延时:嵌入式系统的时间控制基石 1.1 延时基础:从概念到硬件实现 1.1.1 什么是延时? 定义:延时是通过软件或硬件手段,使程序执行过程中暂停指定时间,再继续后续操作的技术。本质是对时间的精确或…...
2025 FIC wp
这次比赛计算机和手机大部分题目都比较常规 第一和第四部分有点让人摸不着头脑 比赛的时候第一部分有四个题没出 第四部分基本都没怎么出 现在复盘一下 把我当时做题的心得和获取的新知识记录一下 互联网取证的部分就先学习一下别的师傅 检材 链接:https://pan.bai…...
STM32标准库和HAL库SPI发送数据的区别-即SPI_I2S_SendData()和HAL_SPI_Transmit()互换
1、标准库SPI初始化 这是标准库的SPI初始化配置 2、HAL库SPI初始化 这是HAL库函数的SPI初始化配置 可以看出,基本一直,除了 基本的io口配置区别,其他主要的读写函数不用动的。 3、SPI发送函数_替换对比 /* SPI写入一个字节 */ void SP…...
Cesium 三维场景中通过自定义着色器实现多种特效(纹理、光带、点光源、反射)
整体功能概述 构建一个基于 Cesium 的三维场景,加载三维瓦片集模型,同时提供多种特效,像夜景纹理、上下扫光、点光源以及反射纹理等,利用 dat.gui 库创建交互界面。 代码详解 白膜特效 nightFs.glsl void fragmentMain(Fragm…...
Java学习--HashMap
HaspMap是Java集合框架中最重要、最常用的数据结构之一。其基于哈希表实现了Map接口,在Java1.8的版本中,其采用了“数组链表红黑树”的混合结构,底层代码如下: transient Node<K,V>[] table; // 哈希桶数组 static class N…...
Monorepo、Lerna、Yarn Workspaces、pnpm Workspaces 用法
Monorepo 介绍 Monorepo是一种方案,而非具体的工具。 Monorepo指的是将多个相关的项目或模块放在同一个代码仓库中进行管理的方式。这种方案有以下优点: 方便代码共享:不同项目或模块之间可以方便地共享代码、组件、工具函数等,…...
JVM指令手册:深入理解字节码执行机制
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 引言 Java虚拟机(JVM)作为Java生态的核心执行引擎,其指令系统是理解程序底层运行机制的关键。本手册将系统解析JVM指令集…...
springboot logback 默认加载配置文件顺序
在 Spring Boot 应用中,Logback 默认加载配置文件的顺序遵循特定的规则。以下是详细的加载顺序和优先级说明: 1. 默认配置文件加载顺序 Logback 在 Spring Boot 中会按以下顺序查找并加载配置文件(优先级从高到低): l…...
用 Nodemon 解决 npm run serve 频繁重启服务
Nodemon 是一个基于 Node.js 构建的开发工具,专为帮助开发者自动监控项目文件的更改而设计。每当文件发生变更时,Nodemon 会自动重启 Node.js 服务器,无需手动停止并重启。这对于提升开发速度、减少人工操作非常有帮助,尤其适用于…...
WEB安全--社会工程--SET钓鱼网站
1、选择要钓鱼的网站 2、打开kali中的set 3、启动后依次选择: 4、输入钓鱼主机的地址(kali)和要伪装的网站域名: 5、投放钓鱼网页(服务器域名:80) 6、获取账号密码...
系统架构师---基于规则的系统架构
引言 在业务规则高度动态且需快速响应的系统中,基于规则的系统架构风格(Rule-Based System Architecture Style)提供了一种将业务逻辑与代码解耦的标准化范式。从保险理赔的自动化审核到金融风控的实时拦截,规则引擎已成为企…...
嵌入式软件--stm32 DAY 4 中断系统
1.课后练习 学了这么长时间,现在让我们第一次做练习。 1.1往返流水灯 1.1.1 LED1-LED2-LED3-LED2-LED1循环 (1)工程准备 复制上一个寄存器实现的工程文档,删减修改我们正要实现的工程。为了区别练习和学习工程,我们…...
android开发制作aosp系统签名文件给普通apk签名使用
platform.pk8和platform.x509.pem复制出来放在同一目录下 将AOSP源码路径下build\target\product\security\platform.pk8和platform.x509.pem复制出来放在同一目录下 新开一个ternimal窗口执行下面命令,生成platform.pem文件 openssl pkcs8 -in platform.pk8 -info…...
AVL树的介绍与学习
目录 1.前言 2.AVL树 3.AVL树的插入 平衡因子的更新 更新停止的条件 旋转 1.前言 在学习了二叉搜索树,set和map之后,我们接下来趁热打铁,继续学习AVL树。 2.AVL树 1.AVL树具有二叉搜索树的性质,但是它的左右子树的高度差不…...
docker部署Mysql8一直密码错误记录
正常流程是这样得: 第一步 #拉镜像 docker pull mysql:8.0 第二步 #运行名为 mysql8 得容器 ,MYSQL_ROOT_PASSWORD123456 设置密码 docker run -p 3307:3306 \ --name mysql8 \ -e MYSQL_ROOT_PASSWORD123456 \ -v /docker/mysql8/data:/var/lib/m…...
智慧水库与AI深度融合的实现方案及典型应用场景
以下是智慧水库与AI深度融合的实现方案及典型应用场景,结合行业前沿案例与技术架构展开: 一、智慧水库AI实现方案 1. 技术架构与核心工具 感知层: 多模态传感器网络:部署毫米波雷达水位计(精度3mm)、光纤光栅渗压计(分辨率0.01%FS)、高清智能球机(支持800万像素+AI分…...
大语言模型架构基础与挑战
大语言模型(Large Language Model, LLM)在近几年引领了自然语言处理领域的革命性进展。这类模型通常拥有极其庞大的参数规模(往往达到数十亿乃至数千亿级别),通过对海量文本数据进行自监督训练,展现出卓越的语言理解和生成能力。自2018年前后第一批大语言模型问世以来,基…...
KAG:通过知识增强生成提升专业领域的大型语言模型(二)
目录 摘要 Abstract 1 实验 1.1 实验设置 1.2 总体结果 1.3 消融研究 1.3.1 知识图谱索引消融 1.3.2 推理与检索消融 1.3.3 实验结果与讨论 2 KAG服务部署 2.1 安装Docker 2.2 安装Doker Compose 2.3 启动服务 2.4 查看状态 2.5 产品访问 3 KAG 0.6使用&#x…...
【Luogu】动态规划六
P1586 四方定理 - 洛谷 思路: 这题其实就是完全背包问题,但是有限制,最多数量只能是 4 所以我们可以定义 dp[i][j] 为 i 用 j 个数拼凑的总方案数 那么转移方程也很明显了,dp[i][j] dp[i - k*k][j - 1] 具体的,我…...
Postman接口测试: postman设置接口关联,实现参数化
🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 postman设置接口关联 在实际的接口测试中,后一个接口经常需要用到前一个接口返回的结果, 从而让后一个接口能正常执行,这个…...
docker打开滚动日志
在 Docker 中启用滚动日志(log rotation)可以帮助你管理容器日志的大小,避免日志文件占用过多磁盘空间。以下是具体的操作步骤: 1. 修改 Docker 守护进程配置 Docker 的日志配置是通过 daemon.json 文件管理的。你需要修改此文件…...
单片机-89C51部分:5、点亮LED
飞书文档https://x509p6c8to.feishu.cn/wiki/SlB5wYD1QiPRzWkfijEcIvv8nyc 一、应用场景 二、点灯原理 插件led灯珠长引脚为正极,短引脚为负极。 LED(发光二极管)两端存在电压差,有一定的电流流过时会亮起。电流可以理解为水流,…...
Lua 第10部分 模式匹配
10.1 模式匹配的相关函数 字符串标准库提供了基于模式的 4 个函数。 我们已经初步了解过函数 find 和 gsub,其余两个函数分别是 match 和 gmatch (Global Match 的缩写)。 函数 string.find 用于在指定的目标字符串中搜索指定的模式。最简单的模式就是一…...
Maven 4.0.0 模式-pom.xml配置详解
Maven 4.0.0 模式-pom.xml配置详解 此 pom.xml 文件涵盖了 Maven 4.0.0 模式支持的所有主要标签,包括项目元数据、依赖管理、构建配置、发布管理等。每个标签都配有详细注释,说明其作用、常见用法和可能的值。 此文件旨在展示标签的完整性&#…...
IDEA 连接 Oracle 数据库
IDEA 连接 Oracle 数据库...
机器人快速启动
机器人快速启动 ES机器人开机操作流程 方法一(一体化底座启动) 接通48V电源点击底座“Power”按钮观察电源指示灯亮起,蜂鸣器发出“嘀”声,代表底座启动完成 方法二(控制手柄启动) 长按手柄开关机键2秒后松…...
使用 MediaPipe 和 OpenCV 快速生成人脸掩膜(Face Mask)
在实际项目中,尤其是涉及人脸识别、换脸、图像修复等任务时,我们经常需要生成人脸区域的掩膜(mask)。这篇文章分享一个简单易用的小工具,利用 MediaPipe 和 OpenCV,快速提取人脸轮廓并生成二值掩膜图像。 …...
《全球反空间能力》报告翻译——部分1
全球反空间能力 已进行过破坏性反卫星测试的国家 美国 美国目前拥有世界上最先进的军事太空能力,尽管与中国的相对差距正在缩小。在冷战期间,美国开创了许多现今使用的国家安全太空应用,并在几乎所有类别中保持技术领先地位。美国军方在将…...
云原生课程-Docker
一次镜像,到处运行。 1. Docker详解: 1.1 Docker简介: Docker是一个开源的容器化平台,可以帮助开发者将应用程序和其依赖的环境打包成一个可移植的,可部署的容器。 docker daemon:是一个运行在宿主机(DO…...
组件的基本知识
组件 组件的基本知识 组件概念组成步骤好处全局注册生命周期scoped原理 父子通信步骤子传父 概念 就是将要复用的标签,抽离放在一个独立的vue文件中,以供主vue文件使用 组成 三部分构成 template:HTML 结构 script: JS 逻辑 style: CSS 样…...
空间矩阵的思考
今天又看了些线性代数,引发了许多思考。 矩阵是以长和宽存储数据,那有没有一种新型的矩阵,以长宽高的形式存储数据呢?我不知道有没有,所以暂且称其为空间矩阵。 它肯定是存在的,可以这样抽象&#…...
【数据挖掘】时间序列预测-常用序列预测模型
常用序列预测模型 (1)AR(自回归)模型(2)ARIMA模型(3)Prophet模型(4)LSTM模型(5)Transformer模型(6)模型评估6.…...
将你的本地项目发布到 GitHub (新手指南)
目录 第 1 步:在 GitHub 上创建新的仓库 (Repository)第 2 步:将本地仓库连接到 GitHub 远程仓库第 3 步:(可能需要) 重命名你的默认分支第 4 步:将本地代码推送到 GitHub第 5 步:在 GitHub 上检查结果后续工作流程 你…...
[论文梳理] 足式机器人规划控制流程 - 接触碰撞的控制 - 模型误差 - 自动驾驶车的安全合规(4个课堂讨论问题)
目录 问题 1:足式机器人运动规划 & 控制的典型流程 (pipline) 1.1 问题 1.2 目标 1.3 典型流程(Pipeline) 1.3.1 环境感知(Perception) 1.3.2 高层规划(High-Level Planning) 1.3.3 …...
初中级前端面试全攻略:自我介绍模板、项目讲解套路与常见问答
为了给面试官留下专业而亲切的第一印象,自我介绍要突出与岗位相关的技能和项目经验,同时以自己擅长的领域开放式结尾。通常可以按照以下思路组织自我介绍内容:首先简单介绍个人信息和工作年限,然后列出精通的前端技术栈…...
Android开发中svg转xml工具使用
要使用 svg2vector-cli 工具通过命令行将 SVG 文件转换为 Android 可用的 XML 矢量图标文件,可以单个文件转换或者整个文件夹批量转换,以下是详细的步骤和说明: 1. 准备工作 1.1 下载工具 首先需要下载 svg2vector-cli-1.0.0.jar 或更高版本…...
爬虫技术入门:基本原理、数据抓取与动态页面处理
引言 在当今数据驱动的时代,网络爬虫技术已成为获取和分析互联网数据的重要手段。无论是搜索引擎的网页收录、竞品数据分析,还是学术研究的语料收集,爬虫技术都发挥着关键作用。本文将深入浅出地讲解爬虫的基本原理,分析它能获取…...
AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年4月27日第65弹
从今天开始,咱们还是暂时基于旧的模型进行预测,好了,废话不多说,按照老办法,重点8-9码定位,配合三胆下1或下2,杀1-2个和尾,再杀6-8个和值,可以做到100-300注左右。 (1)定…...
服务器数据备份,服务器怎么备份数据呢?
企业数据量呈指数级增长,服务器数据备份已成为保障业务连续性、抵御勒索攻击与合规审查的核心技术环节。当前,服务器数据备份方案需兼顾数据完整性、恢复时效性、存储经济性三大核心诉求,其实现路径可根据技术架构、数据规模及容灾等级划分为…...
语音识别质量的跟踪
背景 这个项目是用来生成结构化的电子病历的。数据的来源是医生的录音。中间有一大堆的处理,语音识别,关键字匹配,结构化处理,病历编辑......。最多的时候给上百家医院服务。 语音识别质量的跟踪 一、0225医院的训练后的情况分…...
【数据挖掘】时间序列预测-时间序列的平稳性
时间序列的平稳性 (1)平稳性定义(2)平稳性处理方法2.1 差分法2.2 季节调整(Seasonal Adjustment)2.3 趋势移除(Detrending)2.4 对数转换(Logarithmic Transformation&…...
成都蒲江石象湖旅游攻略之石象湖郁金香最佳观赏时间
石象湖坐落于成都蒲江,拥有绝美的郁金香花海,吸引了很多的游客。如果大家想要观赏比较诱惑人的郁金香,那自然就应该知道正确的观赏时间。 心想郁金香合适的时间是每年的3月份到3月底。石象湖会还会举办盛大的郁金香节,在花园内有数…...
大模型、知识图谱和强化学习三者的结合,可以形成哪些研究方向?
大模型(Large Language Models, LLMs)、知识图谱(Knowledge Graph, KG)与强化学习(Reinforcement Learning, RL)作为人工智能领域的三大核心技术,其融合正推动着认知智能迈向新高度。本文结合2023-2025年的最新研究成果,系统梳理三者结合的七大科研方向及其技术路径。 …...
Linux文件操作
在C语言中,我们已经学习了文件相关的知识,那么在Linux中我们为什么还要再来学习文件呢?这是因为C语言中和Linux中,"文件"是2个不同的概念。所以我们要来学习Linux中对文件的操作。 在学习之前,我们先来回顾一…...
PostSwigger Web 安全学习:CSRF漏洞3
CSRF 漏洞学习网站:What is CSRF (Cross-site request forgery)? Tutorial & Examples | Web Security Academy CSRF Token 基本原理 CSRF Token 是服务端生成的唯一、随机且不可预测的字符串,用于验证客户端合法校验。 作用:防止攻击…...
【Node.js 】在Windows 下搭建适配 DPlayer 的轻量(简陋)级弹幕后端服务
一、引言 DPlayer官网:DPlayer 官方弹幕后端服务:DPlayer-node MoePlayer/DPlayer-node:使用 Docker for DPlayer Node.js 后端(https://github.com/DIYgod/DPlayer) 本来想直接使用官网提供的DPlayer-node直接搭建…...
淘宝tb.cn短链接生成
淘宝短链接简介 1. 一键在线生成淘宝短链接tb.cn,m.tb.cn等 2. 支持淘宝优惠券短链接等淘宝系的所有网址 3. 生成的淘宝短链接是官方的,安全稳定有保证 4.适合多种场景下使用,如:网站推广,短信推广 量大提供api接口࿰…...
在web应用后端接入内容审核——以腾讯云音频审核为例(Go语言示例)
腾讯云对象存储数据万象(Cloud Infinite,CI)为用户提供图片、视频、语音、文本等文件的内容安全智能审核服务,帮助用户有效识别涉黄、违法违规和广告审核,规避运营风险。本文以音频审核为例给出go语言示例代码与相应结…...
优化无头浏览器流量:使用Puppeteer进行高效数据抓取的成本降低策略
概述 使用 Puppeteer 进行数据抓取时,流量消耗是一个重要考虑因素。特别是在使用代理服务时,流量成本可能显著增加。为了优化流量使用,我们可以采用以下策略: 资源拦截:通过拦截不必要的资源请求来减少流量消耗。请求…...
【C语言】fprintf与perror对比,两种报错提示的方法
它们的主要区别在于 信息来源 和 自动包含的系统错误详情。 1. fprintf(stderr, "自定义错误信息\n"); 功能: 这是标准库中的一个通用格式化输出函数。你可以用它向任何文件流(包括 stdout 标准输出, stderr 标准错误, 或任何用 fopen 打开的文件&#x…...