ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TIM定时中断(第一节)

TIM定时中断(第一节) 目录TIM简介定时器类型一句话理解三档对比从强到弱几个名词翻译一下跟我们手上那块芯片的关系实际写代码时怎么选例 2PWM 输出 —— 做 LED 呼吸灯例 3输入捕获 —— 读红外遥控器的按键值三个例子串起来看*底层逻辑选看先看整体信号链一句话第一层时钟源 ——让谁来数第二层预分频器 PSC——把心跳放慢第三层计数器 CNT——真正在数的那个东西第四层自动重装载 ARR——数到几算一轮第五层更新事件 UEV——数到头了该干活了第六层输出比较PWM 那一路——CNT 跟 CCR 对表第七层输入捕获红外解码那一路——外部跳变把 CNT 抓下来把整个流程串一遍以 1ms 中断一次 为例参考手册时的 地图定时中断基本结构整体方向从左往右第一列时钟源谁来给定时器当心跳第二列黄色梯形块时钟模式选择第三列粉色大框 时基单元—— 这才是心脏第四列中断输出控制深青绿色窄条第五列NVIC最右边把整张图连起来读一遍重难点讲解预分频时序先知道这张图在演示什么从上到下逐行翻译整张图连起来讲一个故事以后写代码会遇到的一个坑图下面那个公式计数器时序图先知道这张图在演示什么从上到下逐行翻译整张图连起来讲一个瞬间跟代码对应起来把两张图串起来看看代码时的对应关系TL;DR快速了解定时器本质是一个自动数节拍的计数器给它一个时钟它就一格一格往上加加到设定值就触发中断或翻转引脚。STM32 的定时器按能力分成三档——高级定时器TIM1/TIM8最强支持刹车、死区和互补输出适合电机控制通用定时器TIM2~TIM5够用能输出 PWM、测脉宽、读编码器基本定时器TIM6/TIM7最朴素只会定时到点产生中断。在 STM32F103C8T6 这块芯片上资源被砍了一刀只有 TIM1、TIM2、TIM3、TIM4没有 TIM5~TIM8也没有基本定时器 TIM6/TIM7所以做定时中断实验只能用 TIM2~TIM4 来凑。TIM简介详细图解参见新应用定时器类型一句话理解定时器本质就是个自动数节拍的计数器—— 给它一个时钟它就一格一格往上加加到我们设定的值就 叮 一下触发中断 / 翻转引脚。不同型号的定时器计数器的 手脚多少 不一样所以分成了三档。三档对比从强到弱表格档位芯片里谁是挂在哪条总线能干什么高级定时器最猛TIM1、TIM8APB2通用定时器的活它全干还多了刹车、死区、互补输出—— 专门用来控制电机比如三相无刷电机能安全地驱动大功率器件通用定时器够用TIM2~TIM5APB1能数外部脉冲、测脉宽、输出 PWM、读编码器、几个定时器之间能联动基本定时器最朴素TIM6、TIM7APB1就只会一件事定时到点产生中断顺便能触发 DAC 输出电压波形几个名词翻译一下总线APB1 / APB2可以理解成两条 数据马路。APB2 跑得快一点所以高级定时器挂上面APB1 慢一点但定时器大多挂这儿。不用纠结知道 挂在哪 只是硬件连接写代码时 CubeMX 会自动处理。重复计数器高级定时器才有的数 N 次才真正进一次中断减少 CPU 负担。死区生成 互补输出两个引脚输出反相的方波中间留一点点 都关断 的间隙防止 H 桥上下管直通烧管子。这是电机控制的标配。输入捕获外部信号跳变时自动把当前计数值锁下来 → 可以测频率、测脉宽。输出比较计数值到某个数时引脚自动翻转 / 置位 → 可以输出 PWM、做精确延时。编码器接口直接接旋转编码器的 A/B 两相硬件自动算方向和步数不用你软件去数。跟我们手上那块芯片的关系在STM32F103C8T6 中它引脚少、封装小所以片上资源被砍了一刀✅ 有TIM1高级、TIM2、TIM3、TIM4通用❌ 没有TIM5~TIM8这意味着这块板子上没有基本定时器 TIM6/TIM7也没有第二个高级定时器 TIM8。所以平时做 定时中断 实验只能用 TIM2~TIM4想做最简单的 纯定时也得拿通用定时器来凑。实际写代码时怎么选翻译成代码心智这就是 基本定时器 该干的活但因为F103上没有 TIM6/TIM7所以拿通用定时器 TIM3 来干。例 2PWM 输出 —— 做 LED 呼吸灯用谁TIM2_CH1通用定时器的一个通道对应引脚 PA0事LED 亮度从暗到亮再到暗循环往复不要 CPU 参与。定时器内部发生了啥翻译成代码心智这就是 输出比较 功能通用定时器 TIM2~4 都能干。蜂鸣器发出不同音调、舵机转到不同角度原理一模一样。例 3输入捕获 —— 读红外遥控器的按键值用谁TIM4_CH1通用定时器的输入捕获通道对应 PB6事红外接收头把遥控信号转成一串高低电平想知道每一段高 / 低电平持续了多久从而解出按了哪个键。定时器内部发生了啥这就是 输入捕获 功能——CPU 全程不用盯着引脚硬件帮你把时间戳记下来精度比你软件去读 GPIO 高得多。三个例子串起来看表格例子用到定时器的哪个能力对应F103上的LED 闪烁定时中断基本功能TIM2/3/4 都行呼吸灯 / PWM输出比较TIM2/3/4 的通道红外解码输入捕获TIM2/3/4 的通道电机控制你没做死区 互补输出TIM1核心记忆点现在做的实验LED、按键、蜂鸣器、OLED99% 都用 TIM2/3/4 就够了TIM1 暂时可以放一边。等以后想做循迹小车、平衡车再回头啃 TIM1 的互补输出和刹车功能也不迟。*底层逻辑选看只是想 每 1ms 进一次中断点灯 / 扫描按键 → 随便挑 TIM2/3/4 都行想输出 PWM 调光 / 控制舵机 / 做呼吸灯 → 用 TIM2~TIM4 的通道想读红外遥控器脉冲、测方波频率 → 用 TIM2~TIM4 的输入捕获想做无刷电机 FOC、驱动半桥 → 才需要 TIM1你这块板子有够用想要个 免费 的定时器顺带驱动 DAC → F103这块板子没有 TIM6/7得用 TIM2~4 模拟例 1最朴素的 定时中断—— 让 LED 每 500ms 闪一次定时器TIM3通用定时器F103有需要做的事主程序该干嘛干嘛每过 500ms 自动进一次中断函数把 LED 翻转一下。定时器内部流程变化定时器有个时钟输入比如 72MHz经过一个 分频器 把它降慢。假设我们想要 1ms 跳一格就分频 72 倍 → 每 1ms 计数器加 1。计数器从 0 开始往上加加到 自动重装载值 等于 500 时就触发一次中断。进中断函数你把 LED 翻转计数器自动清零重新开始数。分频系数 PSC 71 → 72MHz / (711) 1MHz自动重装载 ARR 500 → 数 500 次就是 500ms中断里写HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);还是先分频、再数到 ARR跟例 1 一样。但这次不触发中断了而是把 当前计数值 跟你设的一个 比较值CCR做比较计数值 CCR → 引脚输出高电平计数值 ≥ CCR → 引脚输出低电平这样一个周期里高电平占多少比例就由 CCR 决定 —— 这就是占空比。你只要在主循环里慢慢改 CCR 的值比如从 0 加到 ARR再减到 0LED 就呼吸起来了。定时器周期还是定死的比如 1kHzCCR 0→ LED 全灭CCR ARR/2→ 一半亮度CCR ARR→ LED 全亮CPU 只需要偶尔改一下 CCR波形是硬件自动生成的。定时器照样在那儿数格子比如 1μs 一格。关键区别不是 CPU 去读时间而是当引脚上出现 下降沿高变低的瞬间硬件自动把当前计数值锁存到一个寄存器里同时触发一个中断。你在中断里把这个值读出来跟上次锁存的值一减就是这段电平持续了多少微秒。红外协议比如 NEC就是靠 高电平 9ms 低电平 4.5ms 这种特定脉宽组合来表示 0 和 1 的你把这些脉宽串起来就解出按键码了。计数器 分频器 是所有定时器的 地基三档都有。基本定时器只会 数到顶就喊一声。通用定时器还会 盯着引脚跳变记时间 和 自动按规则改引脚电平。高级定时器在通用基础上再加 安全刹车 和 成对输出反相波为电机这种危险场合准备。这次把定时器 剖开 看内部 —— 信号是怎么从一个时钟晶振一步步变成引脚翻转和中断的。你写配置文件的时候每一个参数背后都对应着里面某一个模块。先看整体信号链一句话时钟源 → 分频器 (PSC) → 计数器 (CNT) → 跟 ARR 比 → 触发更新事件 → 跟 CCR 比 → 改输出引脚所有定时器都是这条流水线区别只在于后面挂了多少个 比较 / 捕获通道。第一层时钟源 ——让谁来数定时器得有个心跳才能数心跳来源有四种内部时钟 (CK_INT)就是芯片主时钟你板子上 72MHz最常用。点灯、做 PWM 都用它。外部输入引脚 (TIx)从某个 GPIO 进来的方波定时器跟着外部方波数。外部触发输入 (ETR)专门一个引脚用来数高速外部脉冲。级联其他定时器一个定时器溢出后给下一个当心跳。比如 TIM1 溢出一次TIM2 才加 1—— 两个定时器拼起来变成一个 大计数器。代码选 Clock Source Internal Clock选的就是这一层。第二层预分频器 PSC——把心跳放慢晶振 72MHz 太快了你根本数不了这么细。PSC 就是一个除法器本质PSC 是个 16 位寄存器写啥值时钟就被切成 (PSC1) 份。CubeMX 里那个 Prescaler 框填的就是 PSC。第三层计数器 CNT——真正在数的那个东西CNT 是一个寄存器里面存着当前计数值每来一个经过 PSC 分频后的脉冲它就 1。工作模式有三种你现在只需要知道前两种关键点CNT 是硬件自动加的CPU 完全不用管。你任何时刻去读它都能拿到 现在数到几了—— 这就是定时器的灵魂不占用 CPU硬件自己跑。第四层自动重装载 ARR——数到几算一轮ARR 也是个寄存器规定了 数到几就清零重新开始。ARR 还有个影子寄存器的概念暂时不用深究你写 ARR 的时候不是立刻生效而是等下一次 溢出 才换上去防止改一半导致时序错乱。第五层更新事件 UEV——数到头了该干活了当 CNT 加到等于 ARR 的那一刻硬件自动做三件事这就是你例 1 里 每 500ms 进一次中断 的底层真相不是定时器喊你是它把 UIF 置 1中断控制器看到后通知 CPU。你代码里那句if(__HAL_TIM_GET_FLAG(htim3, TIM_FLAG_UPDATE))读的就是这个 UIF。第六层输出比较PWM 那一路——CNT 跟 CCR 对表这是另一条并行的分支跟上面的 溢出 无关每个通道CH1/CH2/CH3/CH4独立一套每个通道都有一个CCR 寄存器捕获比较寄存器你往里写一个数值。定时器内部有个比较器每个时钟周期都在做一件事整个 ARR 周期里高电平占 CCR/ARR 这么长一段 —— 这就是占空比。PWM 模式下流程是这样的你写PSC 71它就把时钟频率除以 (711) 72。72MHz / 72 1MHz也就是每1 微秒计数器加 1。你写PSC 999就变成 72MHz/1000 72kHz每 13.89μs 加 1。向上计数0 → ARR → 清 0 → 再 0 → ARR…… 循环最常用例 1 例 2 都是这种。向下计数ARR → 0 → 重装 ARR……中央对齐0 → ARR → 0上下走电机控制才用。PSC 决定 每格多少时间ARR 决定 一共多少格。一次完整定时 (PSC1) × (ARR1) / 时钟频率。比如 PSC71, ARR999每格 1μs数 1000 格 1ms这就是 1ms 中断一次的由来。CNT 清 0向上计数模式下。置一个标志位 UIF在状态寄存器 SR 里—— 告诉全世界我溢出了。如果中断使能位 UIE 开着就向 NVIC 申请一次中断CPU 跳进你的TIM3_IRQHandler()。CNT CCR→ 引脚输出电平 A比如高CNT ≥ CCR→ 引脚输出电平 B比如低CNT 从 0 开始往上加。CNT CCR引脚一直是高。CNT 刚到 CCR引脚 啪 翻成低。CNT 加到 ARR溢出、CNT 清 0。CNT 从 0 重新开始引脚又变回高。周而复始。第七层输入捕获红外解码那一路——外部跳变把 CNT 抓下来这是反过来的方向信号从引脚进来定时器帮你记时间。引脚跳变时内部流程关键从 引脚跳变 到 CNT 被锁进 CCR是纯硬件完成的中间没有任何 CPU 指令。哪怕你 CPU 正被别的中断占着这个时间戳也不会丢 —— 只是你晚一点去读而已。这就是为什么输入捕获比你在主循环里while(GPIO_Read() 0);这种软件计时要准得多。把整个流程串一遍以 1ms 中断一次 为例假设你配的是 TIM372MHz 时钟PSC71ARR99972MHz晶振 ↓ PSC分频器72MHz / 72 1MHz1μs一格 ↓ CNT每个μs10,1,2,3,...,999 ↓ CNT加到999ARR的那一刻 ├→ CNT自动清0下一个μs重新从0开始 ├→ UIF标志置1 └→ 如果UIE开了CPU跳进 TIM3_IRQHandler() ↓ 你写翻转LED、读按键、扫描OLED……如果同时你又开了 PWM 通道CNT在数0,1,2,...,999 ↓ 每个μs跟CCR比一下 CNT CCR500 → PA0输出高 CNT ≥ 500 → PA0输出低 ↓ ARR到 → CNT清0 → 下一轮 ↓ PA0上自动冒出一串方波占空比 500/1000 50%参考手册时的 地图STM32 参考手册里定时器那一章本质就是把上面每个模块单独讲了一遍表格你以后会看到的寄存器对应我上面讲的哪一层PSC第二层 分频器CNT第三层 计数器当前值ARR第四层 数到几清零CCR1/2/3/4第六 / 七层 比较值 / 抓到的时间戳DIER中断使能第五 / 七层 要不要通知 CPUSR状态第五 / 七层 UIF/CCxIF 标志位CR1控制启动 / 停止、计数方向你写 HAL 库代码的时候HAL_TIM_Base_Start()干的事就是把 CR1 里的 启动位 拉高从那一刻起 CNT 才开始动。在此之前PSC、ARR、CCR 这些值你都可以慢慢配好定时器一动不动等你发令。一句话总结定时器就是个全自动节拍器—— 你提前告诉它 每格多久、数几格、到点干什么剩下的事它硬件自己跑CPU 该干嘛干嘛到点它主动敲你门。这就是为什么单片机做 同时干很多事 特别顺手一个定时器干一件事几个定时器一起跑互不打架。呼吸灯效果的真相主循环里每隔几毫秒改一下 CCR 的值0→ARR→0硬件就自动把 PWM 占空比改了引脚电平翻转全程是定时器自己干的CPU 一秒钟都没参与。CubeMX 里 PWM 模式选 PWM mode 1配的就是这个比较器的翻转规则。定时中断基本结构引脚上出现下降沿或上升沿。先经过一个滤波器可以设几个周期延迟防止毛刺误触发。边沿检测器确认 真的跳变了。硬件自动把此刻的 CNT 值复制到 CCR 寄存器里—— 这个动作叫 捕获。同时置一个捕获标志 CCxIF如果开了中断就跳进中断函数。你在中断里读 CCR 的值跟上一次读的减一下就是这段电平持续了多少个 刻度。整体方向从左往右[左边选心跳] → [中间粉色真正在数的核心] → [右边把结果通知CPU]第一列时钟源谁来给定时器当心跳最左边这一排绿色 / 红色方块都是 候选心跳源定时器从中挑一个用表格图上的方块大白话对应之前讲的RCC 内部时钟红芯片自己的 72MHz 晶振最常用时钟源第 1 种ETR 外部时钟绿接 GPIO从一个专门引脚喂进来的外部方波时钟源第 3 种ITRx 其他定时器绿别的定时器溢出一次给我当一次心跳时钟源第 4 种级联TIx 捕获通道绿接 GPIO从捕获引脚进来的方波当心跳时钟源第 2 种中间那个黄色块梯形就是一个多路开关—— 你写代码选哪种时钟源就把哪一路信号接通到右边去。我们用得最多的是最上面那个红块内部时钟其他几路平时基本不碰。小细节TIx 那一路箭头同时也向右指向 时基单元意思是这个引脚既可以当心跳源也能当输入捕获用 —— 一个引脚两种用途由你配置决定。第二列黄色梯形块时钟模式选择上面已经说了它就是个 多路选择开关图上列了四种模式内部时钟模式 外部时钟模式 2走 ETR 进来的 外部时钟模式 1走 TIx 进来的 编码器模式TI1TI2 两相一起进专门给旋转编码器用你不用记这些名字知道它就是个二选一 / 多选一的开关就行。第三列粉色大框 时基单元—— 这才是心脏这个粉色框圈起来的就是我上次说的 计数器 分频器 ARR 三件套也是图里最重要的部分。按信号流粉色框内部信号流时钟进来 → PSC砍慢 → CNT往上加 → CNT跟ARR比 ↓ 加到ARR那一刻 → 溢出 → CNT清0重来第四列中断输出控制深青绿色窄条CNT 溢出的那一刻除了清 0 重来还会往外发一个 信号。这个信号先经过 中断输出控制 这个门 ——你这个门开着它才允许出去门关着没开中断使能信号就被拦下来。对应我之前讲的以 TIM3 每 1ms 进一次中断 为例RCC内部时钟(72MHz) ↓ [黄色选择器] 选了内部时钟模式 ↓ PSC预分频器72MHz / 72 1MHz1μs一格 ↓ CNT计数器每μs1 → 0,1,2,...,999 ↓ CNT 跟 ARR(999) 比 → 加到999的瞬间 ├→ CNT清0重新从0开始 └→ 往外发溢出信号 ↓ [中断输出控制]这个门开着 ↓ NVIC ↓ CPU跳进你写的 TIM3_IRQHandler()底下那个 运行控制 方块就是你调用HAL_TIM_Base_Start_IT(htim3)的那一下 —— 把总开关拉上去整条流水线才开始跑。重难点详解这张图你以后翻参考书时先看粉色框—— 那是定时器的本体左右两边都是 入口 和 出口是配角。这个 中断输出控制 方块就是那个 门。第五列NVIC最右边NVIC 是芯片内部的 中断总管—— 所有外设定时器、串口、外部引脚……的中断申请都先汇到这里它按优先级决定先处理谁、后处理谁然后叫 CPU 跳进去执行对应的中断函数。在代码里看到的 NVIC 配置中断优先级就是在管这个最右边的方块。把整张图连起来读一遍PSC 预分频器左下橙色方块左边进来的时钟先经过它。它把时钟频率砍慢比如 72MHz 砍成 1MHz。就是之前讲的第二层。CNT 计数器右下橙色方块PSC 出来的每一下CNT 就 1。它是核心 —— 硬件自动往上加。就是之前讲的第三层。ARR 自动重装载器右上橙色方块跟 CNT 之间有个双向箭头。意思是CNT 不停地拿自己跟 ARR 比一旦加到等于 ARR就触发 溢出然后 CNT 被 ARR 的值重新装填、清 0 重来。双向箭头表示 两个寄存器互相影响。就是之前讲的第四层。运行控制最底下橙色方块箭头向上指向 CNT这是个总开关。你调用HAL_TIM_Base_Start()的时候就是在抬这个开关 —— 从这一刻起 CNT 才开始动之前 PSC、ARR 的值都配好了但 CNT 一动不动。UIF 标志位被置上在 SR 寄存器里。中断使能位 UIE 开着才真的往 CPU 递申请。为什么 TIM1 在 APB2、TIM2 在 APB1—— 图上没画出来但 RCC 那个红块内部就分了两路总线高级定时器走 APB2 那根 快车道通用和基本定时器走 APB1 那根。PSC、ARR、CNT 到底什么关系—— 粉色框里三个方块一目了然PSC 管快慢、CNT 在数、ARR 管数到几停。外部时钟、编码器模式什么时候用—— 左边那几个绿块就是为这俩场景留的入口你现在用不到知道有这东西就行。以 TIM3 每 1ms 进一次中断 为例RCC内部时钟(72MHz) ↓ [黄色选择器] 选了内部时钟模式 ↓ PSC预分频器72MHz / 72 1MHz1μs一格 ↓ CNT计数器每μs1 → 0,1,2,...,999 ↓ CNT 跟 ARR(999) 比 → 加到999的瞬间 ├→ CNT清0重新从0开始 └→ 往外发溢出信号 ↓ [中断输出控制]这个门开着 ↓ NVIC ↓ CPU跳进你写的 TIM3_IRQHandler()底下那个 运行控制 方块就是你调用HAL_TIM_Base_Start_IT(htim3)的那一下 —— 把总开关拉上去整条流水线才开始跑。重难点讲解为什么 TIM1 在 APB2、TIM2 在 APB1—— 图上没画出来但 RCC 那个红块内部就分了两路总线高级定时器走 APB2 那根 快车道通用和基本定时器走 APB1 那根。PSC、ARR、CNT 到底什么关系—— 粉色框里三个方块一目了然PSC 管快慢、CNT 在数、ARR 管数到几停。这张图先看粉色框—— 那是定时器的本体左右两边都是 入口 和 出口预分频时序先知道这张图在演示什么初始状态PSC 寄存器里写的是 0 → 分频系数 01 1 → 不分频CK_CNT CK_PSC。中途发生一件事在代码里往 TIMx_PSC 寄存器写了个新值 1。结果分频系数变成 2 → CK_CENT 频率减半。这张图就是把 从写入新值到真正生效 这一瞬间硬件内部每个信号的变化都画出来了。从上到下逐行翻译第 1 行CK_PSC预分频器的输入时钟就是那个连续跳变的方波。它一直在跑不管你配什么它都不会停。第 2 行CNT_EN计数器使能信号。一开始是低没启动中间拉高一跳 —— 这就是你调用HAL_TIM_Base_Start()的那一下。从这一刻起后面所有信号才开始动。第 3 行定时器时钟 CK_CNT真正喂给 CNT 计数器的时钟。前半段PSC 还没改的时候跟 CK_PSC 一模一样一个跟着一个跳。后半段新 PSC 生效后频率变成一半——CK_PSC 跳两下CK_CNT 才跳一下。这就是 分频 的肉眼可见效果。第 4 行计数器寄存器CNT 的值每个 CK_CNT 脉冲来一下CNT 就 1。像上面标的F7 → F8 → F9 → FA → FB → FC → 00 → 01 → 02 → 03...这是十六进制数。FC 加到 FF 再溢出就变 00—— 图上正好在 00 那里冒了一个 UEV 脉冲。注意一个细节前半段CK_CENT 快的时候F7,F8,F9,FA,FB,FC 这些数挤得很密每跳一格 CNT 就 1。后半段CK_CENT 慢了之后00,01,02,03 之间间隔变宽了 —— 因为现在每来一个 CK_CENT 脉冲要等两个 CK_PSC。第 5 行更新事件 UEVCNT 从 FC 溢出清 0 到 00 的那一瞬间啪 冒出来一个窄脉冲。这就是之前讲的 溢出事件—— 它是个分水岭。第 6 行预分频控制寄存器这就是手册里的TIMx_PSC 寄存器本体。前半段写的是 0。中间箭头指的地方变成 1—— 这就是你代码里写htim.Instance-PSC 1;的动作。第 7 行在 TIMx_PSC 中写入新数值这一行其实就是第 6 行那个箭头的注释告诉你 这里是 CPU 写寄存器的瞬间。第 8 行预分频缓冲器影子寄存器关键来了第 6 行 预分频控制寄存器 在箭头那一刻立刻就变成 1 了但这一行的 预分频缓冲器并没有跟着立刻变—— 它还停在 0一直等到 UEV 脉冲那个时刻才 啪 地变成 1。这就是我上次提到的影子寄存器缓冲器机制你写 PSC 寄存器写进去的是 前台值。真正起作用的是 后台影子值。影子值不是立刻换而是等到下一次 UEV更新事件才把新值搬上来。为什么要这样设计防止你改 PSC 改到一半前一个周期用老分频、后一个周期用新分频导致时序错乱。STM32 故意让你 写了不算等下一个整周期才生效。第 9 行预分频计数器这是个藏在 PSC 内部的小计数器范围是 0 到 PSCPSC0 的时候它只能数到 0 → 每来一个 CK_PSC 就溢出一次 → CK_CENT 跟 CK_PSC 同频。PSC1 的时候它从 0 数到 1数满 2 次才溢出一次 → CK_CENT 频率被砍一半。图上后半段你能看到它在 0,1,0,1,0,1 之间来回跳 —— 这就是 数到 1 就翻回 0每两个 CK_PSC 产生一个 CK_CENT。整张图连起来讲一个故事按时间顺序一开始PSC0预分频系数 1。CNT_EN 拉高后CNT 开始跟着 CK_PSC 飞快地数F7,F8,F9...中间某一刻你代码里往 PSC 写了个新值 1。PSC 寄存器立刻变成 1但缓冲器还是 0所以分频行为没变CNT 继续飞快地数。CNT 溢出从 FC 加到 00冒了一个 UEV 脉冲。UEV 那一瞬缓冲器才把新值 1 搬上去预分频计数器切换成 数到 1 才溢出 模式。从此刻开始CK_CENT 频率减半CNT 变慢01,02,03... 之间间隔变成原来的两倍。以后写代码会遇到的一个坑这个影子寄存器机制带来一个实际后果你运行中改 PSC/ARR 的值不会立刻生效要等下一次 UEV。比如你定时器正在跑你忽然写htim.Instance-PSC 100;这一瞬间 CNT 还在用老分频系数继续数直到下一次溢出清 0 的那一刻才换新参数。如果你想让它立刻生效得手动触发一次更新事件htim.Instance-PSC 100; SET_BIT(htim.Instance-EGR, TIM_EGR_UG); // 手动产生UEV立刻刷新影子寄存器但日常开发里99% 的情况你不需要这么干—— 都是在初始化时把 PSC/ARR 配好启动后就不动了。图下面那个公式CK_CNT CK_PSC / (PSC 1)这就是核心公式记住这个比记图有用PSC0 → 分频 1 倍 → 72MHz 直跑PSC71 → 分频 72 倍 → 1MHz你最常用PSC999 → 分频 1000 倍 → 72kHz为什么是 PSC1 而不是 PSC因为 PSC 写 0 也得有意义 —— 写 0 就表示 别分频硬件内部算的时候自动给你 1。这是所有 STM32 寄存器的统一设计写 0 代表 1 倍。一句话总结这张图PSC 是个有 延迟生效开关 的分频器 —— 你写下新分频值它先记着不动等定时器整周期走完一圈UEV才真正切换避免半路改参数导致时序错乱。计数器时序图这张图比上一张简单讲的是CNT 数到顶溢出那一瞬间所有信号怎么配合。图标题写了 分频因子为 2就是说 PSC 已经配好了CK_CENT 是 CK_INT 的一半频率。先知道这张图在演示什么PSC 已经固定分频因子 2CK_CENT 稳定跑着。CNT 一路从 0 往上数数到 ARR 后溢出。图上截到的瞬间是CNT 从 0x0036 溢出回 0x0000 那一刻。也就是说这张图里ARR 0x0036十进制 54CNT 要从 0 数到 55 才算一轮。从上到下逐行翻译第 1 行CK_INT芯片内部时钟一直在跳跟你无关。第 2 行CNT_EN计数器使能拉高之后 CNT 才开始数。图上这个上升沿在很左边说明早就启动了。第 3 行CK_CNT定时器时钟经过 PSC 分频后的时钟。注意它跟 CK_INT 的关系CK_INT 跳一下CK_CENT 不一定跳。因为分频因子 2所以 CK_INT 跳两下CK_CENT 才跳一下。你看方波的稀疏程度就能看出来。第 4 行计数器寄存器CNT 的值每个 CK_CNT 脉冲来一下CNT 就 10034 → 0035 → 0036 → 0000 → 0001 → 0002 → 0003重点看那个 跳变点从 0036 下一个 CK_CNT 来了按理说应该是 0037但它直接变成了0000—— 这就是溢出。因为 ARR0x0036CNT 数到等于 ARR 之后下一个脉冲就触发溢出自动从 0 重新开始。第 5 行计数器溢出CNT 刚从 0036 跳到 0000 的那一瞬间啪 冒一个窄脉冲。这个脉冲叫 溢出。第 6 行更新事件UEV跟 计数器溢出 几乎同时冒出来。UEV 就是 溢出事件 的正式名字 —— 硬件自动做这几件事CNT 清 0刚才看到的 0000。PSC/ARR 的影子寄存器在这一刻刷新就是上一张图讲的那个机制。往右边发一个信号。第 7 行更新中断标志UIFUEV 那一瞬硬件自动把 SR 寄存器里的 UIF 位置 1。这就是 通知 CPU 的那张纸条如果你开了更新中断DIER 寄存器里 UIE1NVIC 就叫 CPU 跳进中断函数。如果你没开中断UIF 就那么静静挂着你可以随时去读它 —— 比如你不用中断而是主循环里if(htim.Instance-SR TIM_SR_UIF)来轮询。注意 UIF 一旦置 1 就不会自己清 0必须你软件写 0 把它清掉否则下次进中断又会重复触发。整张图连起来讲一个瞬间按时间顺序看那个跳变点CNT0x0036CK_CENT 来一个脉冲。硬件判断CNT1 0x0037 ARR0x0036 → 触发溢出。三件事同时发生CNT 被清成 0x0000。计数器溢出 脉冲冒一下。UEV 脉冲冒一下UIF 标志位置 1。如果中断开着CPU 跳进TIMx_IRQHandler()。你在中断里做两件事该干嘛干嘛翻转 LED、读传感器……。手动把 UIF 写 0 清掉HAL 库的HAL_TIM_IRQHandler()会帮你做。下一个 CK_CENT 来CNT0x0001继续数。跟代码对应起来假设你要 1ms 进一次中断这块板子 72MHz表格你想配公式选值每格多久CK_CENT CK_INT / (PSC1)想要 1μs 一格 → PSC71数几格算一轮溢出频率 CK_CENT / (ARR1)想要 1ms 一次 → ARR999代入图上的公式CK_CNT_OV CK_CNT / (ARR1) CK_PSC / (PSC1) / (ARR1) 72MHz / 72 / 1000 1000 Hz 每1ms溢出一次这就是你所有定时器中断实验的数学源头。把两张图串起来看你现在应该把上一张图和这张图合起来理解表格上一张图图 99这一张图图 102主角PSC 预分频器CNT 计数器演示什么改 PSC 参数时分频系数怎么切换CNT 数到 ARR 时怎么溢出清 0关键机制影子寄存器延迟生效溢出→UEV→UIF→中断触发分水岭UEV 脉冲UEV 脉冲同一个东西两张图里都出现了 UEV—— 它是整个定时器的 节拍点。所有跨周期的事改 PSC、改 ARR、重新装 CNT都等 UEV 这一刻才生效保证永远不会半路乱。看代码时的对应关系// 这些你在 CubeMX 生成的 MX_TIM3_Init() 里都能看到 htim.Init.Prescaler 71; // PSC决定每格多久 htim.Init.CounterMode TIM_COUNTERMODE_UP; // 向上数 htim.Init.Period 999; // ARR决定数到几 HAL_TIM_Base_Start_IT(htim3); // 拉高CNT_EN开始数然后中断函数void TIM3_IRQHandler(void) { HAL_TIM_IRQHandler(htim3); // 它自动读SR、判断UIF、清UIF、调回调 } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM3) { // 这里就是你想让每1ms干一次的事 HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); } }整张图讲的就是这几行代码背后的硬件故事CNT 在数→到顶→UIF 置 1→CPU 进中断→HAL 库帮你清 UIF→你的回调函数被调用。一句话总结定时器就是个 数到顶就举手 的计数器 ——PSC 决定每格多长ARR 决定数几格数满就冒 UEV、置 UIF、叫 CPU。你写代码时填 PSC 和 ARR 两个数字硬件就自动按这个节奏无限循环跑下去。
返回列表