开篇
上篇让 TIM2 数外部脉冲,走的是 ETR 直连的外部时钟模式二(结尾顺带介绍了从模式下的模式一)。除了外部时钟模式一,从模式还有另外三个选项:
| 从模式选项 | 作用 |
|---|---|
| 复位模式 | 触发信号让定时器复位 |
| 门控模式 | 电平控制定时器跑/停 |
| 触发模式 | 触发信号启动定时器 |
外部时钟决定”数什么”,这三个模式决定”怎么数”。下面分开讲。
一、复位模式:触发信号让计数器清零重来
1. 原理
① 触发信号怎么走:本篇走 TI1 通道
上篇的 PA0 走 ETR 直连(外部时钟模式二)。这篇把 PA0 换回输入通道 TI1 的身份,走的是上篇末尾”不同的配置方式”里介绍过的那条路——TI1FP1:
1 | PA0(TI1) → 输入滤波 → 边沿检测 → TI1FP1 → 触发器 → 从模式控制器 |
和 ETR 直连相比,这条路径多经过一道触发器做信号选择;更关键的区别在从模式控制器收到触发脉冲后干什么——上篇是”把它当计数脉冲数一下”,这次是”执行一次复位”:
1 | PA0 ──► [输入滤波×15] ──► [边沿检测:上升沿] ──► TI1FP1 ──► [触发器] ──► 从模式控制器 |
② 复位做了什么:相当于触发了一次更新事件
触发沿一到,从模式控制器执行的动作,相当于定时器自己触发了一次更新事件——和数满 ARR 自动重载产生的是同一个事件。这次更新带来三件事:
- CNT 立刻归零——不管它刚才数到哪(比如 6789),直接回到 0 重新开始
- PSC、ARR 的新值立刻生效——运行中改了预分频或重载值,触发瞬间就生效,不用等这一轮数完
- 产生更新事件——开了更新中断,就会进
HAL_TIM_PeriodElapsedCallback
注意第 3 点:复位同样会进更新中断,回调里并不知道这次是”复位”还是”自动重载”带来的——这正是第二章 TRIGGER 标志区分法的由来。
③ 和自动重载的本质区别:归零的时机不同
| 自动重载 | 复位模式 | |
|---|---|---|
| 谁发起 | 定时器自己(CNT 数到 ARR) | 外部触发信号 |
| 什么时刻 | 时间固定,一轮接一轮 | 说不准,信号一到就复位 |
| PSC/ARR 生效时 | 数满这一轮 | 任何一次触发瞬间 |
那有了自动重载,为什么还需要复位模式?差别在于归零的时机:
- 自动重载:清零跟着时间走——CNT 数满 ARR 就归零,跟传送带转到哪没关系
- 复位模式:清零跟着物理位置走——转够一整圈才清零,清零的瞬间正好对应”走完一圈”
还是传送带测距这个场景,两种做法对比:
| 只用自动重载 | 复位模式 | |
|---|---|---|
| 什么时候清零 | 数满 10000 下就清零(时间固定),圈可能还没转完 | 每转一整圈、触发点扫到传感器时 |
| 读到的 CNT 是什么 | 说不清——可能是圈中某个位置,也可能已经跨圈 | “这一圈从起点走了多远” |
2. CubeMX 配置
新建工程 Tim_SlaveMode,TIM2 配置如下(与上篇的差别:时钟源改回内部、从模式改复位):
| 设置项 | 值 | 说明 |
|---|---|---|
| Clock Source | Internal Clock | 不做外部计数,改回内部 8MHz 时钟 |
| Slave Mode | Reset Mode(复位模式) | / |
| Trigger Source | TI1FP1 | PA0 上的 TI1,滤波后输出 TI1FP1 进触发器 |
| Trigger Polarity | Rising(上升沿) | 上升沿触发复位 |
| Trigger Filter | 15 | 沿用上篇经验,滤掉循迹模块抖动 |
| Prescaler | 8000-1 | 8MHz÷8000=1kHz,1 毫秒计数一次 |
| Counter Period | 10000-1 | 数 10000 下,10 秒自动重载一次 |
| Counter Mode | Up | 从 0 数到 9999 |
| NVIC → TIM2 global interrupt | 勾选 | 开更新中断,观察自动重载 |
串口用阻塞发送 HAL_UART_Transmit 就够——延时要求不严,不需要中断或 DMA。
3. 代码:复位模式
CubeMX 生成后,MX_TIM2_Init 自带 sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET。main.c 的改动全在 USER CODE 区:
1 | /* USER CODE BEGIN Includes */ |
主循环:
1 | while (1) |
| 代码 | 作用 |
|---|---|
HAL_TIM_Base_Start_IT(&htim2) |
置 CEN 并开更新中断 |
__HAL_TIM_GET_COUNTER(&htim2) |
读 CNT 寄存器的值 |
HAL_UART_Transmit |
阻塞发字符串,串口 115200 |
二、怎么区分”复位触发”和”自动重载”?
两种事件都会触发更新中断,代码里怎么分辨?看定时器的触发器中断标志位:
| 标志位 | 宏定义 | 谁置位 | 谁清 |
|---|---|---|---|
| 更新事件标志 | TIM_FLAG_UPDATE |
自动重载 / 复位 | HAL 中断里自动清 |
| 触发器标志 | TIM_FLAG_TRIGGER |
从模式控制器收到触发信号 | HAL 不清,要手动清 |
关键在最后一行:HAL 库只清更新标志,触发器标志得自己清。所以更新中断里先读 TRIGGER:置位说明本次是外部触发(复位模式),没置位就是自然重载。
上面代码里的那个 if 拆开看就是这两步:
1 | /* ① 触发器标志置位了,说明这次更新是外部复位带进来的 */ |
| HAL 宏/函数 | 作用 |
|---|---|
__HAL_TIM_GET_FLAG(htim, TIM_FLAG_TRIGGER) |
读触发器标志是否置位,SET 表示置位(为 1) |
__HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_TRIGGER) |
清掉这个标志;不清的话它一直留着,下次会误判 |
TRIGGER 读完立刻清,否则下一次自动重载进来,标志还是置位状态,会被误判。
三、初始化代码解读
三种模式在代码里的差别,只有从模式那一行的取值(必要时再勾单脉冲)。下面这段代码是触发模式的配置——触发模式 + 单脉冲,如果是复位或者门控模式,只需要变下面提到的一行代码
1 | static void MX_TIM2_Init(void) |
MX_TIM2_Init 里的配置,对应到寄存器的关键位如下:
| 代码字段 | 对应寄存器的位 | 含义 |
|---|---|---|
HAL_TIM_OnePulse_Init(TIM_OPMODE_SINGLE) |
CR1.OPM=1 | 单脉冲模式:数满一轮自动停,等下一次触发 |
TIM_SLAVEMODE_TRIGGER |
SMCR.SMS[2:0]=100 | 从模式:触发(检测到沿 → 启动计数) |
InputTrigger = TI1FP1 |
SMCR.TS[4:0] | 触发信号选 TI1 滤波后的输出 |
TriggerPolarity |
CCER.CC1P | 上升沿有效 |
TriggerFilter=15 |
SMCR.TF[3:0] | 采样 15 次确认(滤抖动) |
TIM_TRGO_RESET |
CR2.MMS[2:0]=000 | 主模式默认:更新事件时 TRGO 复位,本工程用不到 |
复位、门控、触发三种模式,CubeMX 里只改一个下拉,落到寄存器上就是 SMCR 的 SMS[2:0] 三位:
| CubeMX 的 Slave Mode | 代码里的宏 | SMS[2:0] | 行为 |
|---|---|---|---|
| Reset Mode(复位) | TIM_SLAVEMODE_RESET |
010 | 触发信号清零重数 |
| Gated Mode(门控) | TIM_SLAVEMODE_GATED |
011 | 电平开门计数、关门暂停 |
| Trigger Mode(触发) | TIM_SLAVEMODE_TRIGGER |
100 | 触发边沿启动计数 |
| (触发时另勾 One-Pulse) | TIM_OPMODE_SINGLE |
CR1.OPM=1 | 数满一轮自动停止 |
所以三种模式只是 CubeMX 改一个下拉 + 必要时勾单脉冲,生成代码自动对应上述宏,上层代码几乎不用动。
四、门控模式:高电平就数,低电平就停
1. 原理
① “门”是什么:用电平控制计数器的时钟
前面学过的复位、外部计数里,触发信号都是一闪而过的沿;门控模式不一样,它把触发信号看成一把持续的电平闸门,直接控制计数器的时钟通断:
1 | 触发信号(持续电平)──► 从模式控制器(门控模式) |
门打开时计数器照常数,门关上时计数暂停——CNT 拿住当前值一动不动,等门再开,接着数。所以门控的效果是暂停/继续,跟复位那种”清零重来”是两码事。
② 门沿:置位 TRIGGER,但不产生更新事件
开关门的一瞬间(电平跳变沿),从模式控制器同样会置位 TRIGGER 标志——但它不做复位、不产生更新事件(更新事件只在计数溢出时才有)。
所以门控模式下,在更新中断里判断 TRIGGER 没有意义:门沿根本不触发更新中断,回调只在计数溢出时进来;而门沿留下的 TRIGGER 标志,又可能被下一次溢出”带进”回调,造成误判。要判断门的开关,得在主循环里轮询 TRIGGER(见下节代码)。
③ 极性反转
CubeMX 里如果把 Trigger Polarity 改成 Falling(下降沿有效),高低电平对门的控制就反过来:低电平 = 开门计数,高电平 = 关门暂停。
④ 能干什么
门控最有用的场景是测信号的时间宽度:门开多久,计数器就数多久——门的开启时间 = 计数增量 × 计数周期(本篇 1ms),由此反推出一个电平持续了多久。也常用于”条件计数”:只有某个条件满足(门开)时才累计。
2. 配置
CubeMX 只改一处:Slave Mode 从 “Reset Mode” 改成 **”Gated Mode(门控)”**,其余不变。
3. 代码:门控模式
代码改动就一处:TRIGGER 判断从更新中断挪到主循环。原因上一节说了——门沿只置标志、不触发更新中断,放主循环轮询,门沿后最多 2 秒就能抓到,和自动重载互不干扰。
1 | /* 更新中断只发"自动重载"提示,TRIGGER 判断移到主循环 */ |
五、触发模式:边沿一到才开始数
1. 原理
① 触发模式:只负责启动,不负责停止
触发模式只管一件事:检测到触发沿,让定时器开始计数。触发沿过去之后,定时器自己接着数,不需要持续控制:
1 | 触发信号 ──► 从模式控制器(触发模式) |
所以触发模式只启动、不停止。配置好后即使调了 HAL_TIM_Base_Start_IT,计数器也不会自己跑起来——CNT 停在 0,直到触发沿到来。串口一直打印 counter:0,就是还没触发的状态。
② 单脉冲模式:数满一轮自动停
光”启动”还不够,很多场景要”触发一次、数一轮、就停”。这时勾上 One-Pulse Mode(单脉冲模式),硬件的动作变成:
1 | 触发沿 → CEN=1 → 开始计数 → 数满 ARR → 更新事件 + 硬件自动清 CEN → 停止 |
单脉冲模式下,计数器数满溢出、产生更新事件的同时,硬件自动把 CEN 清零,定时器随之停止,等下一次触发沿再来。
| 常规模式 | 单脉冲模式 | |
|---|---|---|
| 数满之后 | 自动重载,接着循环 | 自动停止,等下一次触发 |
| 应用 | 连续计数 | “一次触发、一轮计数” |
③ 能干什么
触发模式最典型的场景:外部事件来了才开始计时。比如光电开关扫到一下 → 定时器启动 → 数满一轮(固定时长)→ 自动停;再来一下再数一轮,每轮之间互不干扰。配合单脉冲,就是一个触发脉冲 → 自动数一轮 → 自动停。
2. 配置
CubeMX 改两处:
| 设置项 | 值 |
|---|---|
| Slave Mode | Trigger Mode(触发模式) |
| One-Pulse Mode | 勾选(Single Pulse) |
这个配置下,定时器默认不会自己启动计数——HAL_TIM_Base_Start_IT 打开的是”等待触发”的状态,没有触发信号时 CNT 一直停在 0。
3. 代码:触发模式 + 单脉冲
1 | /* USER CODE BEGIN 0 */ |
六、三种模式对比总表
| 复位模式 | 门控模式 | 触发模式 | |
|---|---|---|---|
| 干的事 | 触发 = 清零重来 | 门开就数、门关就停 | 触发 = 开跑(配单脉冲:跑完一轮停) |
| 对计数器 | 复位到 0 | 暂停/继续(不丢) | 启动计数 |
| 门沿 / 触发沿 | 产生更新事件 | 只置 TRIGGER,无更新事件 | 只置 TRIGGER,无更新事件 |
| 计数溢出 | 有更新事件 | 有更新事件 | 有更新事件 |
| TRIGGER 标志 | 复位时置位 | 门沿置位 | 触发沿置位 |
| 实用场景 | 每圈归零测距 | 选通计数、测门时间 | 触发后一轮计时 |
怎么选:归零用复位;暂停/继续用门控;触发后跑一轮用触发 + 单脉冲。
七、数据流全链路
以触发模式为例,信号从光电开关一路走到串口:
1 | 光电开关 DO ──► PA0(TI1) ──► 输入滤波(15次) ──► 上升沿检测 |
八、上电时多出的第一次”自动重装载”
烧录后上电,还没到 10 秒周期,串口往往先打出一条”自动重装载”。原因是:MX_TIM2_Init 初始化时会把更新标志置位(和上篇 loop 初始化为 -1 抵消的是同一件事),中断一开,CPU 立刻响应一次更新中断。解决:启动前先清一次更新标志。
1 | __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE); /* 启动前清一次更新标志 */ |
清标志有两套写法,效果一样——都是清同一个更新标志:
HAL_TIM_ClearFlag(&htim2, TIM_FLAG_UPDATE)HAL_TIM_ClearIT(&htim2, TIM_IT_UPDATE)
清这一次之后重启,就不会再多打一条。
九、效果展示
十、实操步骤
- CubeMX 新建工程
Tim_SlaveMode,主频保持 8MHz(理由见上篇滤波章节) - TIM2:Clock = Internal Clock、PSC=8000-1、Period=10000-1、向上计数
- Slave Mode = Reset Mode、Trigger = TI1FP1、极性 Rising、Filter=15
- 勾选 TIM2 global interrupt(NVIC);USART2 115200
- 生成代码,补齐 main.c 的 include / PV / 回调 / 启动 / 主循环(见上文)
- 编译烧录,接串口线(USART2 对应调试口),波特率 115200
- 接线:光电开关 DO → PA0,VCC/GND 接 3.3V/GND(与上篇一致,PA0 复用 TI1 或 ETR)
- 编译烧录,对照”效果展示”一节观察复位模式现象
- 依次把 Slave Mode 改成门控、触发(+单脉冲),对照”效果展示”观察三档表现差异
总结
| 新知识点 | 记住这点 |
|---|---|
| 复位模式 | 触发信号清零计数器并触发更新中断(TRIGGER 标志可以区分) |
| 门控模式 | 高电平计数 / 低电平暂停,门沿只置 TRIGGER 不触发更新中断 |
| 触发模式 | 触发信号让计数开始跑,跑满一轮停(配单脉冲用) |
| TRIGGER 标志 | 从模式控制器置位,HAL 不自动清,记得手动清 |
| 单脉冲模式 | 重载一次后自动停止,配合触发模式使用 |
对比上篇:上篇把从模式当成外部脉冲的”计数源”;这篇把从模式当成定时器的”开关闸门”。
参考资源
- 【STM32】一看就懂的定时器从模式讲解(keysking 第18集):https://www.bilibili.com/video/BV1mU421o7vt/
- 波特律动文档站
- 上篇:STM32——定时器外部时钟


