STM32双定时器实现四路输入捕捉与四路PWM输出实战
简介:本文介绍如何在STM32F103ZE微控制器上仅使用两个定时器(TIM)实现四路输入信号捕捉和四路PWM输出控制,无需主从模式配置。通过合理利用定时器的多通道输入捕获和互补输出功能,可在电机驱动、LED控制和电源管理等需要高精度时间同步的场景中实现高效资源利用。文章详细讲解了定时器的工作模式配置、中断处理机制及同步策略,并结合HAL/LL库进行代码实现,确保系统实时性与稳定性。 
1. STM32定时器(TIM)基础原理与工作模式
定时器核心架构与计数机制
STM32通用定时器基于可编程16位向上、向下或中央对齐计数器,由内部时钟或外部触发驱动。其核心组件包括自动重载寄存器(ARR)、预分频器(PSC)和计数器(CNT),通过配置PSC实现时钟分频,控制计数精度。例如:
TIM3->PSC = 7999; // 假设系统时钟72MHz → 分频后计数时钟为9kHz
TIM3->ARR = 8999; // 溢出周期 = (8999+1)/9000 ≈ 1秒
TIM3->CR1 |= TIM_CR1_CEN; // 启动计数
该结构支持精确时间基准生成,为输入捕捉与PWM输出提供共同时基。
输入捕捉与PWM输出的硬件逻辑
输入捕捉通过ICx映射引脚信号至捕获通道,利用TIxFPx同步输入并触发CCx事件,锁存CNT值至CCR寄存器。而PWM输出则依赖比较匹配机制:当CNT与CCR相等时,输出电平翻转,结合OCxM模式位配置可生成边沿或中心对齐波形。
主从模式与触发控制(TSI/ETR)
定时器支持主从模式协调多外设行为。通过从模式控制器(SMCR)配置ITRx或ETR为外部时钟源,实现精准同步。例如使用ETR引脚作为外部触发输入,可使多个定时器在统一事件下启动,确保多通道测量或控制的时间一致性,为复杂应用奠定硬件协同基础。
2. 输入捕捉模式配置与边沿检测实现
输入捕捉(Input Capture)是STM32定时器模块中极为关键的功能之一,广泛应用于频率测量、脉宽调制分析、编码器信号解码以及时间戳记录等高精度时序控制场景。其核心思想是利用定时器的计数器对特定外部事件发生时刻进行“打标”,即在指定信号跳变(上升沿、下降沿或双边沿)到来时,将当前计数值锁存至捕获/比较寄存器(Capture/Compare Register, CCR),从而实现对外部脉冲的时间参数精确提取。这一机制不仅依赖于硬件层面的精准同步设计,还需配合GPIO复用配置、滤波策略、中断处理及DMA协同传输等多种技术手段才能发挥最大效能。
深入理解输入捕捉的工作流程,必须从底层硬件结构入手。STM32的通用和高级定时器均支持多通道输入捕捉功能,每个通道可独立配置为输入模式,并通过内部互联逻辑与外部引脚建立映射关系。当输入信号满足预设的触发条件时,定时器会自动执行一次捕获操作,同时可选择性地触发中断或DMA请求,以便CPU及时获取数据并进行后续处理。整个过程强调低延迟、高可靠性和抗干扰能力,尤其在面对高频、抖动或噪声较大的信号源时,合理的配置策略显得尤为重要。
此外,现代嵌入式系统对实时性的要求日益提高,使得输入捕捉不仅要完成基本的数据采集任务,还需兼顾多通道并发管理、时间基准统一、事件分类识别等复杂需求。例如,在电机控制系统中,需要同时监测四路编码器A/B相信号以计算转速与方向;在超声波测距应用中,则需精确记录发射与回波之间的时间差。这些应用场景都对边沿检测精度、响应速度和系统稳定性提出了极高挑战。因此,掌握输入捕捉的完整配置链条——从引脚映射到滤波设置,再到中断服务与数据解析——成为开发高性能定时器驱动程序的基础技能。
本章将围绕输入捕捉的核心机制展开系统性剖析,重点探讨信号采样原理、GPIO协同配置、时间戳生成方式以及精度优化手段。通过结合STM32F4系列微控制器的实际寄存器结构与HAL库API使用范例,揭示如何在不同工作条件下实现稳定可靠的边沿检测。同时,引入数字滤波器、预分频控制和DMA辅助传输等高级特性,进一步提升系统的鲁棒性与效率。最终目标是构建一个可扩展、低误触发、高精度的多通道输入捕捉框架,为后续章节中的四路信号同步测量与PWM联动控制提供坚实支撑。
2.1 输入捕捉的工作原理与信号采样机制
输入捕捉的本质是一种基于事件驱动的时间测量技术,其实现依赖于定时器内部计数器与输入通道之间的精密协同。每当外部信号出现符合设定条件的电平跳变(如上升沿)时,定时器便立即将当前计数器值复制到对应的捕获寄存器(CCRx)中,形成一个“时间戳”。该时间戳可用于后续计算脉冲宽度、周期、相位差等关键参数。整个过程无需CPU主动轮询,完全由硬件自动完成,极大提升了响应速度与测量精度。
2.1.1 捕获通道的输入滤波与时钟同步
在实际应用中,外部输入信号往往伴随着噪声或机械开关抖动,若不加以处理,可能导致多次误触发,严重影响测量结果。为此,STM32定时器提供了内置的 数字滤波器 功能,可通过配置捕获/比较模式寄存器(CCMRx)中的ICxF[3:0]字段来启用。该滤波器基于连续采样与多数判决原则,只有当输入信号在连续多个定时器时钟周期内保持一致状态时,才认定有效跳变发生。
| 滤波器配置值 | 所需连续相同样本数 | 适用场景 |
|---|---|---|
| 0b0000 | 无滤波 | 高频纯净信号 |
| 0b0001 | 2个 | 轻度噪声环境 |
| 0b0100 | 8个 | 强干扰或机械触点 |
| 0b1011 | 6个 | 编码器信号去抖 |
// 示例:配置TIM3_CH1输入滤波(使用CK_INT时钟,8次采样)
TIM3->CCMR1 &= ~TIM_CCMR1_IC1F; // 清除原有滤波设置
TIM3->CCMR1 |= (0b1000 << TIM_CCMR1_IC1F_Pos); // 设置滤波器为f_DTS/32/8
代码逻辑逐行解析:
- 第一行使用按位与操作清除 TIM_CCMR1 寄存器中IC1F字段的原值,确保不会叠加错误配置。
- 第二行通过左移 0b1000 (即十进制8)到 IC1F 对应位置,启用8次连续采样滤波,适用于抖动严重的开关信号。
值得注意的是,滤波器所依赖的采样时钟可以来自内部定时器时钟(CK_INT)或经过预分频后的专用时钟(CK_DTS)。通常建议使用经过预分频的时钟以降低功耗并避免过高采样率导致资源浪费。例如,若APB总线时钟为84MHz,经1:2预分频后得到42MHz DTS时钟,再通过1:32分频得到约1.3MHz采样频率,足以应对大多数工业级信号。
为了保证信号采样的准确性,所有输入捕捉操作都必须经过 同步链路 处理。原始GPIO信号首先进入边沿检测电路,随后通过两级同步触发器(双D触发器)接入定时器时钟域,防止亚稳态(metastability)引发的数据错误。此过程虽引入约2~3个时钟周期的延迟,但显著提高了系统可靠性,特别是在跨时钟域通信或多速率系统中至关重要。
flowchart LR
A[外部GPIO信号] --> B(输入滤波器)
B --> C{是否通过滤波?}
C -- 是 --> D[边沿检测单元]
C -- 否 --> E[丢弃]
D --> F[同步至定时器时钟域]
F --> G[触发CCR锁存]
G --> H[设置CCIF标志位]
H --> I[可选: 触发中断/DMA]
上述流程图清晰展示了从原始信号输入到最终产生捕获事件的完整路径。其中,同步环节位于滤波之后、边沿检测之前,确保即使信号存在短暂毛刺,也能在进入主逻辑前被有效抑制。这种设计体现了STM32在硬件层面对于实时系统稳定性的高度重视。
2.1.2 上升沿、下降沿及双边沿触发条件设置
STM32允许用户灵活选择触发捕获的边沿类型,具体通过配置捕获/比较使能寄存器(CCER)中的CCxE、CCxP和CCxNP位实现。以TIMx_CH1为例:
CC1E= 1:使能通道1输入捕获CC1P= 0/1:设置上升沿/下降沿触发CC1NP(仅适用于复合通道):用于设置互补边沿
对于普通通用定时器通道,常用组合如下:
| 配置模式 | CC1P | CC1NP | 触发条件 |
|---|---|---|---|
| 上升沿触发 | 0 | - | 低→高跳变 |
| 下降沿触发 | 1 | - | 高→低跳变 |
| 双边沿触发 | 不支持单寄存器直接设置 | 需软件切换极性 |
由于标准定时器不原生支持双边沿同时捕获,常见做法是在每次捕获后动态更改极性并重启捕获。以下为HAL库实现双边沿检测的经典模式:
static uint32_t last_timestamp = 0;
static uint32_t pulse_width = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
uint32_t current = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
// 判断当前是上升沿还是下降沿(通过极性判断)
if (__HAL_TIM_GET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1) == TIM_INPUTCHANNELPOLARITY_RISING) {
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
last_timestamp = current;
} else {
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
pulse_width = current - last_timestamp;
// 此处可发送脉宽数据至队列或处理函数
}
}
}
参数说明与逻辑分析:
- HAL_TIM_ReadCapturedValue() :读取CCR寄存器中的锁存值,单位为计数周期。
- __HAL_TIM_GET_CAPTUREPOLARITY() :宏定义,读取当前捕获极性。
- __HAL_TIM_SET_CAPTUREPOLARITY() :修改极性设置,实现双边沿交替捕获。
- 每次捕获后切换极性,确保下一次相反边沿也能被捕获,从而实现完整脉冲测量。
该方法虽简单有效,但在高频信号下可能因中断响应延迟导致丢失边缘。更优方案是使用两个独立通道分别监听上升沿和下降沿,或借助高级定时器的冗余输入功能实现真正意义上的双边沿并行捕获。
综上所述,输入捕捉的信号采样机制是一个融合了硬件滤波、时钟同步与边沿识别的综合系统。正确配置滤波参数与触发极性,不仅能提升测量精度,还能有效抑制误触发,为构建高可靠性时间测量系统奠定基础。后续章节将进一步探讨多通道协同与中断优化策略,延续这一技术主线。
3. 四路输入捕捉的多通道定时器设计(TIM1/TIM3)
在现代嵌入式系统中,对多个外部事件进行高精度时间测量是一项常见而关键的任务。尤其在电机控制、编码器信号处理、超声波测距以及工业自动化等应用场景下,往往需要同时监测四个独立的数字脉冲信号,并准确记录其上升沿、下降沿或完整周期的时间信息。STM32系列微控制器中的高级定时器(如TIM1)和通用定时器(如TIM3)提供了强大的输入捕捉功能,支持多通道同步采集与精确时间戳生成。本章节聚焦于如何利用 TIM1 与 TIM3 构建一个稳定可靠的 四路输入捕捉系统 ,涵盖资源规划、硬件协同、中断管理及高频信号处理策略,旨在实现毫秒级甚至纳秒级的时间分辨率。
3.1 多通道输入系统的资源规划与分配
设计一个多通道输入捕捉系统时,首要任务是合理规划可用定时器资源,避免外设冲突并最大化性能利用率。STM32F4/F7/H7等主流系列通常配备多个定时器,其中 TIM1 是高级控制定时器 ,具有更强的功能集;而 TIM3 属于通用定时器 ,适用于大多数标准输入/输出场景。两者均可用于输入捕捉,但在通道数量、触发机制、DMA能力等方面存在差异。
3.1.1 TIM1与TIM3外设特性对比及其适用场景
为明确选择依据,需深入比较两者的寄存器结构、通道能力与扩展功能。下表列出了关键参数对比:
| 特性 | TIM1(高级定时器) | TIM3(通用定时器) |
|---|---|---|
| 时钟源 | APB2(最高可达系统主频) | APB1(通常为系统频率的一半) |
| 捕获/比较通道数 | 4 个独立通道(CH1~CH4),支持互补输出 CHxN | 4 个独立通道(CH1~CH4) |
| 输入滤波器 | 支持每个通道独立配置(ICxF[3:0]) | 支持,但带宽略低 |
| 数字滤波器采样时钟分频 | 可选 CK_INT, CK_INT/2, …, /8 | 同样支持,但仅限部分型号 |
| DMA 请求能力 | 强大,支持 Capture/Update 多请求源 | 支持基本 Capture 和 Update DMA |
| 主从模式控制 | 支持多种触发输入(ITRx, ETR, TIx)作为外部时钟 | 支持 ETR 和 TIx 作为外部时钟 |
| 编码器接口模式 | 支持增量编码器解码(X2/X4 模式) | 支持 X2/X4 编码器模式 |
| 高级功能 | 死区插入、互补输出、刹车功能(BKIN)、重复计数器 | 无互补输出或死区控制 |
从上表可见, TIM1 更适合复杂控制系统 ,例如带有 PWM 输出与反馈捕捉结合的应用;而 TIM3 则更适合纯输入测量任务 ,尤其是当不需要互补信号或高级同步机制时。对于四路独立脉冲信号的捕捉需求,若所有信号频率较低(<1MHz),可优先使用 TIM3 以节省高级定时器资源;若涉及高速信号或后续需与其他 PWM 动作同步,则建议将部分通道分配至 TIM1。
此外,还需注意 GPIO 复用映射限制 。例如,在 STM32F407 中:
- TIM1_CH1 映射到 PA8 或 PE9
- TIM3_CH1 映射到 PA6、PB4 或 PC6
因此,在 PCB 布局前必须确认引脚是否冲突,并根据实际布线便利性调整定时器分配方案。
// 示例:初始化 TIM3 通道1 输入捕捉(PA6)
void MX_TIM3_InputCapture_Init(void) {
__HAL_RCC_TIM3_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef gpio = {0};
gpio.Pin = GPIO_PIN_6;
gpio.Mode = GPIO_MODE_AF_PP;
gpio.Pull = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
gpio.Alternate = GPIO_AF2_TIM3; // PA6 -> AF2 = TIM3_CH1
HAL_GPIO_Init(GPIOA, &gpio);
htim3.Instance = TIM3;
htim3.Init.Prescaler = 83; // 假设系统时钟为 84MHz -> 1MHz 计数频率
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 0xFFFF; // 自动重载最大值
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_IC_Start(&htim3, TIM_CHANNEL_1);
}
代码逻辑逐行分析:
__HAL_RCC_TIM3_CLK_ENABLE():使能 TIM3 的时钟电源,这是任何外设操作的前提。__HAL_RCC_GPIOA_CLK_ENABLE():开启 PA 端口时钟,否则无法配置 PA6。GPIO_InitTypeDef结构体用于定义引脚属性,设置为复用推挽模式(AF_PP),确保信号能正确传入定时器内部。Alternate = GPIO_AF2_TIM3表示将 PA6 设置为 TIM3 的第二功能复用。htim3.Init.Prescaler = 83实现了从 84MHz 系统时钟分频至 1MHz 计数频率(即每滴答 1μs),便于后期时间计算。HAL_TIM_IC_Start()启动输入捕捉功能,此时定时器开始监听 PA6 上的边沿事件。
该初始化流程体现了 时钟使能 → 引脚配置 → 定时器参数设定 → 功能启动 的典型嵌入式驱动顺序。
3.1.2 四路信号在双定时器间的合理分布原则
面对四路输入信号,若全部集中在一个定时器上可能导致中断负载过高或通道竞争。合理的分布策略应遵循以下三项基本原则:
-
按信号频率分级分配
高频信号(>500kHz)建议接入 TIM1,因其挂载在更高频率的 APB2 总线上,计数精度更高;低频信号可交由 TIM3 处理。 -
按功能相关性分组
若某些信号属于同一传感器(如正交编码器 A/B 相),应尽量放在同一个定时器内,以便启用编码器接口模式(Encoder Mode),简化软件解析。 -
平衡中断负载与响应延迟
不宜让单个定时器承担过多中断源。例如,可将通道1~2放于 TIM1,3~4放于 TIM3,通过 NVIC 设置不同优先级,防止中断堆积。
如下图所示,采用 双定时器并行架构 可有效提升系统的并发处理能力:
graph TD
A[外部四路脉冲信号] --> B{信号分类}
B --> C[高频/编码类信号]
B --> D[普通开关量信号]
C --> E[TIM1_CH1 & CH2]
D --> F[TIM3_CH1 & CH2]
E --> G[捕获中断 ISR_TIM1]
F --> H[捕获中断 ISR_TIM3]
G --> I[时间戳队列1]
H --> J[时间戳队列2]
I --> K[主控MCU处理]
J --> K
此架构实现了物理隔离与逻辑统一:虽然数据来自两个定时器,但最终可在主循环或 RTOS 任务中统一处理,形成一致的时间序列视图。
3.2 硬件级联与独立运行模式的选择
为了保证多通道输入信号的时间一致性,特别是在跨定时器场景下,必须构建统一的时间基准。否则,由于各定时器独立运行且初始相位不确定,会导致测量结果出现不可接受的偏差。
3.2.1 基于ETR引脚的外部时钟驱动同步方案
一种高效的解决方案是使用 外部触发输入(ETR, External Trigger Input) 将某一主定时器的时钟输出作为其他定时器的计数源。例如,可将 TIM1 配置为主模式(Master Mode),将其更新事件(UEV)通过 TRGO 引脚输出,再连接至 TIM3 的 ETR 引脚,从而让 TIM3 使用 TIM1 的时钟节奏进行计数。
具体接线方式如下:
- TIM1_TRGO → PA12(或其他映射到 TIM1_ETR 的引脚)
- PA12 → TIM3_ETR(通过PCB走线或跳线连接)
配置步骤如下:
// 配置 TIM1 为主模式,输出 UEV 触发信号
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim1.Instance = TIM1;
// ... 其他初始化省略 ...
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; // UEV 作为 TRGO 输出
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);
// 配置 TIM3 使用外部时钟模式1(来自 ETR)
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_ETRMODE2;
sClockSourceConfig.ClockPolarity = TIM_CLOCKPOLARITY_NONINVERTED;
sClockSourceConfig.ClockPrescaler = TIM_CLOCKPRESCALER_DIV1;
sClockSourceConfig.ClockFilter = 0;
HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig);
参数说明:
TIM_TRGO_UPDATE:表示每当 TIM1 发生更新事件(计数溢出或手动更新),TRGO 引脚输出一个脉冲。TIM_CLOCKSOURCE_ETRMODE2:表示 TIM3 使用 ETR 引脚作为外部时钟源,且由极性、分频和滤波参数进一步控制。ClockFilter=0表示不启用数字滤波,适用于干净信号;若有噪声可设为 4~8 个采样周期。
通过这种方式, TIM3 的计数完全跟随 TIM1 的节奏运行 ,即使它们位于不同的总线(APB1 vs APB2),也能保持严格同步。
3.2.2 共享时基下的统一时间基准构建
另一种更灵活的方式是在软件层面建立“虚拟统一时钟”。即所有定时器均运行在各自的本地时钟下,但通过一次性的 同步启动机制 来对齐起始时刻。
实现方法包括:
1. 所有定时器预设相同 Prescaler 和 Period;
2. 使用同一个 GPIO 触发 START 信号;
3. 通过 OR 逻辑门或 MCU 内部触发链(TSI)同时触发多个定时器启动。
STM32 提供了 内部触发互联(Internal Trigger System, TSI) ,允许一个定时器的事件触发另一个定时器的启动。例如:
// TIM1 触发 TIM3 启动
sMasterConfig.MasterOutputTrigger = TIM_TRGO_ENABLE; // TIM1 开启即发出 TRGO
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
// 在 TIM3 中配置为 ITRx 触发启动
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_ITR0; // ITR0 = TIM1's TRGO
HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig);
HAL_TIM_IC_Start(&htim3, TIM_CHANNEL_ALL);
HAL_TIM_IC_Start(&htim1, TIM_CHANNEL_ALL); // 最后启动主定时器
执行逻辑说明:
- 当调用
HAL_TIM_IC_Start(&htim1)时,TIM1 开始运行,并立即通过 TRGO 输出 ENABLE 信号。- 该信号被路由至 TIM3 的 ITR0 输入端,自动触发 TIM3 启动。
- 由于传输延迟极小(纳秒级),两个定时器几乎同时开始计数。
这种机制无需额外引脚连接,适合高度集成的设计。
3.3 多通道捕获的中断管理机制
当多个通道同时产生捕获事件时,中断服务程序(ISR)的设计直接影响系统的实时性与数据完整性。
3.3.1 每通道独立中断服务程序设计
尽管 STM32 的每个定时器只有一个 IRQn(如 TIM3_IRQn),但可通过判断状态寄存器(SR)中的 CCxIF 标志位来区分具体哪个通道触发了中断。
void TIM3_IRQHandler(void) {
if (__HAL_TIM_GET_FLAG(&htim3, TIM_FLAG_CC1) &&
__HAL_TIM_GET_IT_SOURCE(&htim3, TIM_IT_CC1)) {
uint32_t ts1 = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1);
timestamp_ch1[count_ch1++] = ts1;
__HAL_TIM_CLEAR_IT(&htim3, TIM_IT_CC1);
}
if (__HAL_TIM_GET_FLAG(&htim3, TIM_FLAG_CC2) &&
__HAL_TIM_GET_IT_SOURCE(&htim3, TIM_IT_CC2)) {
uint32_t ts2 = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_2);
timestamp_ch2[count_ch2++] = ts2;
__HAL_TIM_CLEAR_IT(&htim3, TIM_IT_CC2);
}
// 类似处理 CH3, CH4...
}
逻辑分析:
- 使用
__HAL_TIM_GET_FLAG检查是否发生捕获事件。__HAL_TIM_GET_IT_SOURCE确保该事件已启用中断,防止误判。HAL_TIM_ReadCapturedValue读取 CCR 寄存器锁存的时间戳。- 清除标志位至关重要,否则中断会持续触发。
推荐为每个通道维护独立的时间戳缓冲区,便于后期分析脉冲宽度或频率变化趋势。
3.3.2 中断优先级嵌套与响应延迟控制
在四路高速信号场景下,可能短时间内连续触发多个中断。若不妥善管理优先级,低优先级中断可能被长时间阻塞。
使用 NVIC 配置中断优先级:
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0); // 抢占优先级最高
HAL_NVIC_EnableIRQ(TIM3_IRQn);
表格:典型中断优先级划分建议
| 中断类型 | 抢占优先级 | 子优先级 | 说明 |
|---|---|---|---|
| TIM1 Capture | 0 | 0 | 最高优先级,保障高频信号不丢失 |
| TIM3 Capture | 1 | 0 | 次高,确保及时响应 |
| USART Rx DMA | 2 | 0 | 数据转发可容忍轻微延迟 |
| SysTick | 3 | 0 | 系统节拍最低优先级 |
通过上述配置,可实现 抢占式嵌套中断 ,确保关键事件始终优先处理。
3.4 实际应用中的时间测量算法实现
3.4.1 脉冲宽度与周期计算的软件逻辑流程
以测量 PWM 信号为例,需交替记录上升沿与下降沿时间戳:
#define MAX_SAMPLES 100
uint32_t rising_edge, falling_edge;
uint32_t pulse_width[MAX_SAMPLES], period[MAX_SAMPLES];
int edge_state = 0; // 0: 等待上升沿, 1: 等待下降沿
void IC_Callback(uint32_t channel, uint32_t timestamp) {
if (channel == TIM_CHANNEL_1) {
if (edge_state == 0) {
rising_edge = timestamp;
edge_state = 1;
} else {
falling_edge = timestamp;
pulse_width[index] = falling_edge - rising_edge;
period[index] = timestamp - last_rising; // 需保存上次上升沿
last_rising = rising_edge;
index = (index + 1) % MAX_SAMPLES;
edge_state = 0;
}
}
}
配合输入捕捉中断即可实现全自动脉宽提取。
3.4.2 高频信号连续捕获的数据溢出处理策略
当信号频率极高时,16位定时器(如 TIM3)可能发生溢出(ARR=0xFFFF)。此时需启用 更新中断(UIE) 并维护一个 32 位软计数器:
uint32_t overflow_count = 0;
void TIM3_UP_IRQHandler(void) {
if (__HAL_TIM_GET_FLAG(&htim3, TIM_FLAG_UPDATE)) {
overflow_count++;
__HAL_TIM_CLEAR_IT(&htim3, TIM_IT_UPDATE);
}
}
uint32_t get_full_capture_value(void) {
return (overflow_count << 16) | __HAL_TIM_GET_COUNTER(&htim3);
}
这样可将有效测量范围扩展至 2^32 × T_tick,满足绝大多数工程需求。
综上所述,基于 TIM1 与 TIM3 的四路输入捕捉系统不仅具备良好的扩展性与精度,还可通过合理的软硬件协同设计实现高可靠性与高实时性,广泛适用于各类精密测时场合。
4. PWM输出模式与占空比调节机制
脉宽调制(Pulse Width Modulation, PWM)是嵌入式系统中实现模拟信号控制的核心技术之一,广泛应用于电机驱动、LED调光、电源管理等领域。STM32系列微控制器通过其丰富的定时器资源,尤其是高级定时器TIM1和通用定时器TIM3,提供了高度灵活的PWM生成能力。本章将深入剖析PWM在STM32中的硬件生成机制,重点围绕计数方式、比较匹配逻辑、占空比动态调节策略以及波形稳定性的保障手段展开论述。内容不仅涵盖理论层面的运作原理,还将结合寄存器级配置、代码示例与实际应用场景,帮助读者构建从底层到应用层的完整认知体系。
4.1 PWM生成的核心原理与计数方式
PWM的本质是通过周期性地切换数字输出引脚的高低电平,利用占空比(高电平持续时间与整个周期之比)来等效模拟电压值。STM32的定时器通过精确控制计数器(CNT)与捕获/比较寄存器(CCR)之间的关系,实现这一功能。其核心在于“比较匹配”机制——当计数器的当前值等于某个通道的CCR寄存器设定值时,触发预定义的动作,如翻转输出电平。
4.1.1 比较匹配机制与输出电平翻转控制
在PWM模式下,每个定时器通道均可独立配置为输出比较模式,并进一步选择具体的PWM工作模式。以向上计数为例,假设自动重载寄存器(ARR)设为999,则计数器从0递增至999后归零,形成一个完整的周期。若某通道的CCR设置为399,则在计数达到399时发生比较匹配。
根据所选的PWM模式,输出行为不同。例如,在 PWM模式1 中:
- 当 CNT < CCRx:输出低电平
- 当 CNT ≥ CCRx:输出高电平
- 计数溢出(更新事件)后复位状态
而在 PWM模式2 中则相反:
- CNT < CCRx:输出高电平
- CNT ≥ CCRx:输出低电平
这种极性反转的设计允许开发者根据外部电路需求(如共阴极或共阳极连接)灵活选择输出极性。
以下是一个基于LL库配置TIM3_CH1为PWM模式1的代码片段:
// 启用TIM3时钟
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM3);
// 配置CH1为PWM输出模式,有效极性为高
LL_TIM_OC_SetMode(TIM3, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1);
LL_TIM_OC_SetPolarity(TIM3, LL_TIM_CHANNEL_CH1, LL_TIM_OCPOLARITY_HIGH);
// 设置CCR1初始值
LL_TIM_OC_SetCompareCH1(TIM3, 400);
// 使能CH1输出
LL_TIM_CC_EnableChannel(TIM3, LL_TIM_CHANNEL_CH1);
// 设置ARR和PSC以决定频率
LL_TIM_SetAutoReload(TIM3, 999); // 周期 = 1000 ticks
LL_TIM_SetPrescaler(TIM3, 71); // 假设APB1=72MHz → TIM clk = 72MHz/(71+1)=1MHz
// 开始计数
LL_TIM_EnableCounter(TIM3);
代码逻辑逐行分析:
| 行号 | 说明 |
|---|---|
| 1 | 开启TIM3的电源时钟,确保外设可被访问 |
| 3-4 | 将CH1配置为PWM模式1,并指定输出极性为高电平有效(即匹配后变高) |
| 7 | 设定比较值为400,表示在一个1000周期中,前400个周期为低,其余600为高 → 占空比40% |
| 10 | 使能该通道输出,否则即使配置完成也不会驱动GPIO |
| 13-14 | 设置自动重载值ARR=999,预分频PSC=71,使得定时器时钟为1MHz(每tick=1μs),周期为1ms(频率1kHz) |
| 17 | 启动计数器开始运行 |
此配置生成的是标准PWM波形,频率为1kHz,占空比40%,可通过示波器验证。
为了更直观展示PWM生成过程,使用Mermaid流程图描述其内部逻辑流:
graph TD
A[启动定时器] --> B{计数方向?}
B -->|向上计数| C[Cnt++]
B -->|中央对齐| D[先增后减]
C --> E{Cnt == CCRx?}
E -->|是| F[触发OC动作: 翻转/置位/清零]
E -->|否| G{Cnt == ARR?}
G -->|是| H[产生更新事件 UEV, Cnt=0]
G -->|否| C
F --> G
该图清晰地反映了从计数开始到输出变化再到周期重置的全过程,体现了定时器内部状态机的工作节奏。
4.1.2 模式1与模式2的区别及应用场景
虽然PWM模式1和模式2在电气特性上仅表现为输出极性的差异,但在系统设计中具有重要意义。考虑如下两个典型场景:
-
共阴极RGB LED驱动
若三色LED共阴接地,阳极分别接至三个PWM输出口,则需要高电平点亮。此时应选用PWM模式1,使CCR越小亮度越低(因为高电平时间短)。若误用模式2,则可能导致初始全亮造成电流冲击。 -
H桥电机驱动中的死区配合
在半桥或全桥拓扑中,上下管不能同时导通。若高端MOSFET由主通道控制,低端由互补通道控制,则通常希望两者互为反相。此时若主通道采用PWM模式1输出正相PWM,互补通道自然应输出反相波形,这依赖于BDTR寄存器中的极性配置,而原始主通道的模式选择直接影响整体协调性。
下表对比两种模式的关键参数:
| 参数 | PWM模式1 | PWM模式2 |
|---|---|---|
| 匹配前输出 | 低电平 | 高电平 |
| 匹配后输出 | 高电平 | 低电平 |
| 初始状态 | 强制低 | 强制高 |
| 典型用途 | 高电平有效负载(如N-MOS开关) | 低电平有效负载(如PNP晶体管驱动) |
| 极性灵活性 | 可通过OCPOL反转 | 同样支持 |
| 与互补通道同步性 | 更易实现同步关闭 | 需注意相位一致性 |
值得注意的是,无论哪种模式,都必须启用 预装载功能(Preload Enable) ,否则CCR的修改会立即生效,导致当前周期内出现非预期的脉冲宽度跳变,破坏波形完整性。后续章节将进一步详述该机制。
4.2 占空比动态调节技术
在实际控制系统中,占空比往往需要实时调整,例如PID算法调节电机速度、环境光感测调节背光亮度等。因此,如何安全、精准地更新CCR寄存器成为关键问题。
4.2.1 实时修改CCR值实现精确调制
最直接的方式是在程序运行过程中调用API函数修改CCR寄存器值。以LL库为例:
void SetDutyCycle(TIM_TypeDef *TIMx, uint32_t channel, float duty) {
uint32_t arr = LL_TIM_GetAutoReload(TIMx);
uint32_t compare = (uint32_t)(duty * (arr + 1)); // duty ∈ [0,1]
switch (channel) {
case LL_TIM_CHANNEL_CH1:
LL_TIM_OC_SetCompareCH1(TIMx, compare);
break;
case LL_TIM_CHANNEL_CH2:
LL_TIM_OC_SetCompareCH2(TIMx, compare);
break;
// ...其他通道
}
}
参数说明:
TIMx:指向定时器寄存器结构体,如TIM3channel:指定目标通道编号duty:期望占空比,范围0.0~1.0arr + 1:总周期长度(ARR为最大索引,故总数为ARR+1)- 返回值
compare作为新的CCR值写入
执行逻辑分析:
该函数将浮点形式的占空比转换为整数型比较值,并通过LL库封装的宏写入对应寄存器。然而,若未启用CCR寄存器的缓冲机制,此次写操作将 立即生效 ,可能打断正在进行的周期,造成毛刺或短暂失控。
例如:原占空比50%(CCR=500),突然改为10%(CCR=100),若发生在周期中间且无缓冲,则下半周期将提前结束高电平,导致瞬时占空比仅为(100+100)/1000=20%,而非预期的10%。
4.2.2 利用更新事件确保波形完整性
为避免上述问题,STM32引入了 双缓冲机制(Double Buffering) :CCR寄存器分为“影子寄存器(Shadow Register)”和“预装载寄存器(Preload Register)”。用户写入的是预装载寄存器,只有在更新事件(UEV)发生时,才将预装载值传送到影子寄存器并真正影响输出。
启用方法如下:
// 启用CCR1寄存器预装载
LL_TIM_OC_EnablePreload(TIM3, LL_TIM_CHANNEL_CH1);
// 必须同时开启ARPE位(Auto-Reload Preload Enable)
LL_TIM_EnableARRPreload(TIM3);
一旦启用,所有对 LL_TIM_OC_SetCompareCHx() 的调用都将写入缓冲区,直到下一个更新事件(计数器溢出或软件触发)才生效。这样可以保证每次更改都在完整周期边界执行,维持波形的连续性和稳定性。
此外,还可以通过强制更新事件来立即刷新所有缓冲寄存器:
LL_TIM_GenerateEvent_UPDATE(TIM3); // 触发UEV,同步加载新参数
此操作常用于多轴同步控制,比如机器人关节同时启动运动,需所有PWM通道在同一时刻切换占空比。
4.3 死区时间与互补输出CHxN的设计意义
在电力电子系统中,特别是使用H桥或半桥拓扑时,上下桥臂的开关器件(如MOSFET或IGBT)绝不能同时导通,否则会造成直通短路,严重损坏电源和器件。为此,STM32高级定时器(如TIM1)提供 互补输出(Complementary Output) 和 死区插入(Dead-Time Insertion) 功能。
4.3.1 高端/低端驱动信号互锁需求分析
以一个典型的半桥单元为例:
- 上管(High-Side Switch)导通 → 输出接V+
- 下管(Low-Side Switch)导通 → 输出接地
- 若两者同时导通 → V+直接短接到GND → 大电流烧毁
因此,必须在关断一个管子之后,延迟一段时间再开通另一个,这段延迟称为“死区时间(Dead Time)”,一般为几百纳秒至几微秒,取决于器件开关速度和PCB寄生参数。
STM32的TIM1支持每对通道(CH1/CH1N, CH2/CH2N等)输出互补PWM信号,并可在BDTR(Break and Dead-Time Register)中配置死区时间。
4.3.2 BDTR寄存器配置与短路防护机制
BDTR寄存器包含多个关键字段,用于控制死区、紧急刹车和输出使能。以下是常用位域说明:
| 位段 | 名称 | 功能 |
|---|---|---|
| DTG[7:0] | Dead-Time Generator | 设置死区时间,具体计算见下文 |
| LOCK[1:0] | Lock Configuration | 锁定BDTR配置防止误改 |
| OSSI | Off-State Selection for Idle Mode | 空闲状态下输出状态 |
| OSSR | Off-State Selection for Run Mode | 运行模式下关闭通道的行为 |
| BKE | Break Input Enable | 使能外部刹车信号 |
| MOE | Main Output Enable | 主使能位,必须置1才能输出 |
死区时间由DTG[7:0]编码决定,其计算公式依赖于定时器时钟频率。例如,当TDTS = CK_INT(内部时钟),则:
- DTG[7:5] = 0xx:死区时间 = DTG[7:0] × Tdtg,其中Tdtg = 1 / f_TIM
- DTG[7:5] ≠ 0xx:使用倍乘机制扩展范围
示例代码配置死区时间为1.2μs(f_TIM = 72MHz → Tdtg ≈ 13.89ns):
// 计算:1.2us / 13.89ns ≈ 86.4 → 取86
LL_TIM_BDTR_SetDeadTime(TIM1, 86);
LL_TIM_BDTR_SetMainOutputEnable(TIM1); // 必须开启MOE
同时需配置互补通道:
LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1);
LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH1N, LL_TIM_OCMODE_PWM1); // 互补同模式
LL_TIM_OC_SetPolarity(TIM1, LL_TIM_CHANNEL_CH1, LL_TIM_OCPOLARITY_HIGH);
LL_TIM_OC_SetPolarity(TIM1, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_LOW); // 反相
LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1 | LL_TIM_CHANNEL_CH1N);
此时,CH1与CH1N输出严格反相,且中间插入固定死区,极大提升了系统的安全性。
4.4 输出波形稳定性保障措施
在高频或动态负载环境下,PWM波形容易因寄存器竞争、中断延迟或电源波动而失真。因此,采取一系列软硬件协同策略以提升稳定性至关重要。
4.4.1 自动重载缓冲与预装载使能策略
正如前文所述,ARR和CCR寄存器均支持预装载功能。若未启用,直接修改ARR会导致周期突变,引发频率抖动。例如,在调光系统中突然改变闪烁频率,会影响用户体验。
启用ARR预装载的方法:
LL_TIM_EnableARRPreload(TIM3); // 启用ARR缓冲
LL_TIM_SetAutoReload(TIM3, new_period); // 新值暂存于预装载寄存器
// 实际生效于下次UEV
结合更新中断,可在UEV发生时统一刷新多个参数:
void TIM3_IRQHandler(void) {
if (LL_TIM_IsActiveFlag_UPDATE(TIM3)) {
LL_TIM_ClearFlag_UPDATE(TIM3);
// 安全更新多个通道占空比
UpdateAllChannelsBasedOnControlLoop();
}
}
这种方式实现了“原子化”参数更新,避免中间态干扰。
4.4.2 频率跳变过程中的过渡平滑处理
当需要大幅调整PWM频率(如从1kHz切换到20kHz),若直接写入新ARR值,即使有缓冲,也会在下一个UEV瞬间跳变,引起电流阶跃响应,可能激发机械共振或电磁噪声。
推荐采用 渐进式变频法 :
#define STEP_SIZE 50
static uint32_t current_period = 999;
const uint32_t target_period = 359; // 20kHz @ 72MHz/(71+1)=1MHz
void SmoothFrequencyTransition(void) {
if (current_period > target_period)
current_period -= STEP_SIZE;
else if (current_period < target_period)
current_period += STEP_SIZE;
else
return; // 已到达目标
LL_TIM_SetAutoReload(TIM3, current_period);
}
该函数在主循环或定时器中断中周期调用,逐步逼近目标频率,实现软切换。STEP_SIZE可根据系统响应速度调整,平衡过渡时间和稳定性。
综上所述,PWM不仅是简单的方波输出,更是涉及计数逻辑、极性控制、安全互锁与动态调节的复杂系统工程。掌握这些细节,方能在高性能控制场合游刃有余。
5. 四路PWM输出的双定时器通道分配(含互补输出CHxN)
在现代嵌入式控制系统中,尤其是电机驱动、电源变换与工业自动化领域,多路高精度、高同步性的PWM信号输出是实现精确功率控制的核心手段。STM32系列微控制器凭借其丰富的定时器资源和灵活的输出模式,支持通过多个定时器协同工作来生成复杂的PWM波形组合。本章聚焦于使用 TIM1高级定时器 与 TIM3通用定时器 构建一个具备四路PWM输出能力,并支持 互补输出(CHxN)功能 的系统架构。重点分析如何合理分配双定时器资源,优化通道布局,确保相位同步性,并规避硬件引脚冲突问题。
5.1 双定时器资源划分与输出能力评估
为了高效地实现四路PWM输出并兼顾未来扩展性,必须对所选定时器外设的功能特性进行深入评估,明确各自在系统中的角色定位。STM32中的TIM1属于 高级控制定时器(Advanced-control Timer) ,而TIM3则为典型的 通用定时器(General-purpose Timer) ,二者在PWM生成能力上存在显著差异。
5.1.1 TIM1高级定时器的6路PWM支持特性
TIM1是一个功能极为强大的16位或32位(视型号而定)定时器模块,专为复杂控制应用设计,广泛用于三相逆变器、数字电源等场景。它具备以下关键特征:
- 支持最多 6路 PWM 输出 (CH1~CH4 + CH1N~CH3N),其中前三个通道可配置为带互补输出的成对结构。
- 内置 死区时间插入机制(Dead-time insertion) ,通过BDTR寄存器编程设置,防止上下桥臂直通短路。
- 提供 紧急刹车输入(Break Input) 功能,在故障发生时可立即封锁所有输出。
- 支持多种计数模式:向上、向下、中央对齐,适用于不同调制策略。
- 具备主从模式接口,可通过TRGO信号触发其他定时器,实现跨定时器同步。
以STM32F407为例,TIM1可通过如下引脚输出互补PWM:
| 通道 | 主输出 | 互补输出 | 对应GPIO引脚(典型) |
|------|--------|----------|-----------------------|
| CH1 | PA8 | PA7 | TIM1_CH1 / TIM1_CH1N |
| CH2 | PA9 | PB0 | TIM1_CH2 / TIM1_CH2N |
| CH3 | PA10 | PB1 | TIM1_CH3 / TIM1_CH3N |
该结构允许我们将TIM1配置为两组完整的互补PWM对,即CH1/CH1N 和 CH2/CH2N,分别用于驱动两个半桥电路或作为两相全桥的一部分。
// 示例:初始化TIM1为两路互补PWM输出
static void MX_TIM1_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
htim1.Instance = TIM1;
htim1.Init.Prescaler = 84 - 1; // 84MHz APB2 → 1MHz计数频率
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 1000 - 1; // 1kHz PWM 频率
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
HAL_TIM_Base_Init(&htim1);
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1); // 启动互补输出CH1N
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_2); // 启动互补输出CH2N
}
代码逻辑逐行解读:
-Prescaler = 84 - 1:假设APB2总线时钟为84MHz,经预分频后得到1MHz的计数时钟(每微秒计一次数)。
-Period = 1000 - 1:自动重载值设为999,因此PWM周期为1000μs,对应频率1kHz。
-HAL_TIM_PWM_Start()用于启动主通道输出;
-HAL_TIMEx_PWMN_Start()是专门用于启动互补通道的API,仅适用于高级定时器如TIM1/TIM8。
- 死区时间需额外配置sBreakDeadTimeConfig.DeadTime参数,单位为纳秒级分辨率。
此配置下,TIM1已能稳定输出两组独立且带死区保护的互补PWM信号,满足大多数双桥臂驱动需求。
5.1.2 TIM3通用定时器的4通道输出扩展潜力
虽然TIM3不具备互补输出和死区插入功能,但其仍具备完整的四通道PWM生成功能,适合用于非桥式负载控制,例如LED调光、风扇转速调节或辅助电源控制。
TIM3的主要优势包括:
- 四个独立捕获/比较通道(CH1~CH4),均可配置为PWM输出;
- 支持DMA请求,便于动态调整占空比;
- 可由内部信号(如TIM1的TRGO)触发启动,实现与高级定时器的同步;
- 引脚复用灵活,常见映射如下:
| 通道 | GPIO引脚 | 复用功能 |
|---|---|---|
| CH1 | PA6 | AF2 |
| CH2 | PA7 | AF2 |
| CH3 | PB0 | AF2 |
| CH4 | PB1 | AF2 |
值得注意的是,当TIM1已占用PA7/PB0/PB1作为CH1N/CH2N/CH3N输出时,若同时启用TIM3的CH2/CH3/CH4,则会出现 引脚冲突 。因此必须提前规划引脚分配,避免资源竞争。
下面是一个基于HAL库的TIM3四路PWM初始化示例:
static void MX_TIM3_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 84 - 1; // 84MHz APB1 → 1MHz计数频率
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 1000 - 1; // 1kHz PWM
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim3);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500; // 初始占空比50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2);
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_3);
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_4);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);
}
参数说明与执行流程解析:
-OCMode = TIM_OCMODE_PWM1表示在计数值小于CCR时输出高电平,大于等于时翻转为低;
-Pulse = 500设置初始比较值,结合Period=999,实现50%占空比;
- 每个通道单独调用HAL_TIM_PWM_ConfigChannel()进行配置;
- 所有通道启动后,即可持续输出独立可控的PWM波形。
综上所述,TIM1负责高安全要求的互补PWM输出,TIM3承担普通负载的多路调制任务,两者形成互补协同关系,共同完成四路及以上PWM系统的构建。
graph TD
A[TIM1: 高级定时器] --> B[CH1/CH1N]
A --> C[CH2/CH2N]
A --> D[支持死区、刹车、同步输出]
E[TIM3: 通用定时器] --> F[CH1 ~ CH4]
E --> G[无互补输出]
E --> H[适合独立调光/调速]
I[系统目标: 四路PWM] --> J{资源分配策略}
J --> K[TIM1输出2组互补PWM]
J --> L[TIM3输出2路独立PWM]
K --> M[共4路有效输出]
L --> M
该流程图清晰展示了双定时器协同工作的整体架构逻辑,强调了功能分工与输出能力整合的过程。
5.2 四路PWM在TIM1上的通道布局与互补组合
尽管TIM3可用于扩展输出数量,但在某些应用场景中,用户希望将全部四路PWM集中在同一高精度定时器上运行,以保证最佳的时间一致性与抗干扰性能。此时,应优先考虑充分利用TIM1的全部通道资源。
5.2.1 CH1/CH1N + CH2/CH2N 构成两组互补对
最典型的配置方式是启用TIM1的CH1与CH1N组成第一组半桥驱动,CH2与CH2N构成第二组。这种结构常见于双电机驱动或双相DC-AC逆变器中。
具体配置步骤如下:
1. 将PA8配置为AF1(TIM1_CH1),PA7配置为AF1(TIM1_CH1N);
2. PA9设为AF1(TIM1_CH2),PB0设为AF1(TIM1_CH2N);
3. 开启TIM1的互补输出使能位;
4. 设置统一的自动重载周期;
5. 分别设定各通道的CCR值以调节占空比;
6. 配置BDTR寄存器插入适当死区时间(如100ns~1μs)。
以下是关键寄存器配置片段(LL库风格):
LL_TIM_SetAutoReload(TIM1, 999); // ARR = 999 → 1kHz
LL_TIM_SetCounter(TIM1, 0);
LL_TIM_EnableCounter(TIM1);
// CH1: 占空比60%
LL_TIM_OC_SetCompareCH1(TIM1, 600);
LL_TIM_OC_EnableOutput(TIM1, LL_TIM_CHANNEL_CH1);
LL_TIM_OC_EnableOutput(TIM1, LL_TIM_CHANNEL_CH1N);
// CH2: 占空比40%
LL_TIM_OC_SetCompareCH2(TIM1, 400);
LL_TIM_OC_EnableOutput(TIM1, LL_TIM_CHANNEL_CH2);
LL_TIM_OC_EnableOutput(TIM1, LL_TIM_CHANNEL_CH2N);
// 死区时间设置(假设时钟为168MHz,TDTS=0)
LL_TIM_BDTR_SetDeadTime(TIM1, 84); // 约500ns死区
LL_TIM_EnableAllOutputs(TIM1); // 使能主+互补输出
逻辑分析:
- 使用LL库直接操作寄存器,减少函数调用开销,提高实时响应速度;
-SetCompareCHx控制占空比;
-EnableAllOutputs()必须调用,否则即使通道使能也无法输出信号;
- 死区时间计算依赖于内部时钟分频,此处84个时钟周期 ≈ 500ns(@168MHz);
5.2.2 独立通道与互补通道混合使用注意事项
在某些特殊情况下,可能需要在同一定时器中混合使用互补通道与独立通道。例如:CH1/CH1N用于驱动H桥,CH3仅作为独立PWM控制散热风扇。
此时需注意以下几点:
- 不同通道的工作模式必须一致(如同为边沿对齐PWM);
- 若某通道未使用其互补端(如CH3N),建议将其对应GPIO设为模拟输入或浮空,以防悬空引发噪声;
- 死区时间对所有启用的互补通道全局生效,不可单独关闭某一通道的死区;
- 中断服务程序中需区分中断来源,避免误处理未启用通道的标志位。
此外,还需关注 重复计数器(Repetition Counter) 的影响——若设置了非零值,PWM更新将在多个周期后才生效,适用于低频调制场合。
5.3 多定时器间PWM相位同步控制
当使用多个定时器输出PWM时,若缺乏同步机制,各波形起始相位可能随机偏移,导致系统行为不稳定,尤其在涉及多轴联动或谐振变换器中尤为明显。
5.3.1 主从触发模式下启动同步机制
STM32提供强大的主从模式(Master/Slave Mode)机制,可通过主定时器的特定事件(如更新事件UEV)触发从定时器的动作。典型方案是将TIM1设为主控器,TIM3设为从属定时器。
配置流程如下:
1. 在TIM1中启用“主模式”(Master Mode),选择 更新事件作为TRGO输出信号 ;
2. 在TIM3中启用“从模式”,选择 ITR2作为触发源(对应TIM1的TRGO) ;
3. 设置从模式为“触发启动”(Trigger Mode),使得TIM3在接收到第一个TRGO信号后开始计数;
4. 调整两者的ARR和PSC参数,使其具有相同频率。
// TIM1主模式配置(HAL)
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; // UEV 触发 TRGO
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);
// TIM3从模式配置
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER;
sSlaveConfig.InputTrigger = TIM_TS_ITR2; // ITR2 ← TIM1's TRGO
HAL_TIM_SlaveConfigSynchro(&htim3, &sSlaveConfig);
参数说明:
-TIM_TRGO_UPDATE表示每当TIM1发生更新事件(计数溢出),便向TRGO引脚发送脉冲;
-TIM_TS_ITR2是内部连接通道,通常绑定到TIM1的输出;
- 必须确保RCC配置中开启了定时器间的同步路径。
5.3.2 使用主更新事件(UEV)触发从定时器初始化
通过上述配置,TIM3会在TIM1的第一个更新事件到来时启动计数,从而实现 零相位差同步启动 。这对于需要严格对齐的应用至关重要。
进一步优化措施包括:
- 使用 延迟启动法 :先开启TIM1,延时若干周期后再释放TRGO信号,确保电源稳定;
- 在运行过程中禁用从定时器重新同步,防止意外重置;
- 若需动态改变频率,应同时修改主从定时器的ARR值,并通过UG位强制更新。
以下表格总结了常见同步方式对比:
| 同步方法 | 实现难度 | 相位精度 | 适用场景 |
|---|---|---|---|
| 软件同时启动 | 简单 | 差(μs级偏差) | 非关键负载 |
| GPIO外接同步 | 中等 | 较好 | 多MCU系统 |
| 主从模式(TRGO) | 中等 | 极佳(ns级) | 高精度驱动 |
| DMA+定时器联合刷新 | 高 | 最优 | 数字电源、RF功放 |
sequenceDiagram
participant CPU
participant TIM1 (Master)
participant TIM3 (Slave)
CPU->>TIM1: Start Timer
TIM1->>TIM1: Count Up to ARR
TIM1->>TIM3: Send TRGO Pulse (on UEV)
TIM3->>TIM3: Start Counting
Note right of TIM3: Phase-aligned start
该序列图直观展示了主从定时器在更新事件驱动下的同步行为。
5.4 输出引脚复用冲突规避与PCB布局指导
即使软件配置正确,若硬件层面存在引脚冲突或布线不当,仍可能导致信号失真、串扰甚至芯片损坏。
5.4.1 同一端口不同AF功能的优先级判断
STM32的AF(Alternate Function)机制允许多个外设共享同一引脚,但同一时刻只能有一个处于激活状态。例如PA7可作SPI1_MOSI或TIM1_CH1N,若同时启用将导致不可预测结果。
解决策略包括:
- 明确划分外设职责,避免交叉占用;
- 使用STM32CubeMX工具自动生成无冲突引脚分配;
- 查询参考手册中AF映射表,确认是否存在物理限制;
- 若必须共用,采用软件切换机制(如临时关闭PWM以传输SPI数据)。
5.4.2 高速信号走线对EMI的影响与应对方案
PWM信号通常含有丰富高频成分,特别是上升/下降沿陡峭时易产生电磁干扰(EMI)。推荐采取以下PCB设计原则:
- 缩短走线长度 :尽量将MOSFET栅极驱动电阻靠近MCU放置;
- 使用地平面隔离 :在PWM信号线下方铺设完整GND层,降低环路面积;
- 增加串联阻尼电阻 (10~100Ω):减缓边沿速率,抑制振铃;
- 远离敏感模拟线路 :如ADC采样线、基准电压线等;
- 差分布线用于互补对 :CHx与CHxN尽量平行布线,增强抗共模干扰能力。
| 设计要素 | 推荐做法 | 不良后果 |
|---|---|---|
| 走线间距 | >3W规则(W为线宽) | 串扰增加 |
| 返回路径 | 近距离回流地 | EMI辐射强 |
| 去耦电容 | 每电源引脚加0.1μF陶瓷电容 | 电压波动大 |
| 层叠结构 | 四层板:Signal-GND-Power-Signal | 信号完整性差 |
遵循以上规范,不仅能提升系统稳定性,还可通过EMC认证测试。
综上所述,构建一个可靠的四路PWM双定时器系统,不仅依赖于正确的寄存器配置,更需要综合考量资源分配、同步机制与物理实现之间的协同关系。唯有软硬结合,方能达到最优控制效果。
6. 定时器中断服务程序设计(输入捕获与更新中断)
6.1 输入捕获中断的响应流程与数据处理
在STM32中,输入捕获中断是实现高精度时间测量的核心机制。当设定的边沿(上升沿、下降沿或双边沿)出现在捕获通道上时,定时器硬件会自动将当前计数值锁存到对应的捕获/比较寄存器(CCR),同时置位中断标志位(CCxIF)。此时若中断已使能,将触发相应的中断请求。
为确保实时性,中断服务程序(ISR)必须高效执行。以下是一个典型的输入捕获中断处理逻辑示例,使用HAL库编写:
void TIM3_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim3); // 调用HAL通用中断处理函数
}
HAL_TIM_IRQHandler() 内部会自动判断中断源,并调用相应的回调函数,如:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
static uint32_t rising_edge_timestamp = 0;
static uint32_t falling_edge_timestamp = 0;
uint32_t pulse_width;
if (htim->Instance == TIM3) {
if (__HAL_TIM_GET_CHANNEL(&htim3) == HAL_TIM_ACTIVE_CHANNEL_1) {
if (__HAL_TIM_IS_CAPTURE_FLAG(&htim3, TIM_CHANNEL_1)) {
if (__HAL_TIM_GET_COMPARE(&htim3, TIM_CHANNEL_1) & 0x80000000) {
// 假设最高位用于标记是否为上升沿
rising_edge_timestamp = htim->Instance->CCR1;
} else {
falling_edge_timestamp = htim->Instance->CCR1;
pulse_width = falling_edge_timestamp - rising_edge_timestamp;
// 处理脉宽数据,例如发送至串口或用于控制逻辑
ProcessPulseWidth(pulse_width);
}
}
}
}
}
上述代码通过检测CCR寄存器值的高位来区分边沿类型(实际应用中可通过配置极性动态切换)。变量 rising_edge_timestamp 和 falling_edge_timestamp 必须声明为 static 或全局变量以保持状态,且应考虑多任务环境下的临界区保护。
| 变量名 | 类型 | 作用 |
|---|---|---|
| rising_edge_timestamp | uint32_t | 存储上升沿时刻的时间戳 |
| falling_edge_timestamp | uint32_t | 存储下降沿时刻的时间戳 |
| pulse_width | uint32_t | 计算得出的脉冲宽度 |
| CCR1 | volatile uint32_t | 定时器捕获寄存器,只读访问 |
注意 :由于中断可能频繁触发,应避免在ISR中执行耗时操作(如浮点运算、printf等),建议仅做数据采集和标志设置,具体处理移至主循环。
6.2 更新中断在PWM控制中的核心作用
更新中断(Update Event Interrupt, UEV)发生在定时器计数器溢出(向上计数达到ARR)或归零(向下计数完成)时。该中断对于PWM控制至关重要,尤其是在需要周期性刷新占空比或同步多个外设的场景中。
启用更新中断的方法如下:
__HAL_TIM_ENABLE_IT(&htim1, TIM_IT_UPDATE);
典型应用场景包括:
- 在每个PWM周期结束时更新下一周期的占空比;
- 实现多轴电机驱动的同步换相;
- 触发ADC采样与PWM波形对齐。
例如,在FOC(磁场定向控制)系统中,常利用TIM1的更新中断启动ADC转换,确保电流采样发生在PWM中点,减少纹波影响。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM1) {
// 启动ADC转换
HAL_ADC_Start(&hadc1);
// 或更新下一时段的PID输出值
UpdatePIDOutput();
}
}
此回调函数由 HAL_TIM_IRQHandler() 自动调用,前提是启用了更新中断并配置了优先级。
6.3 中断优先级配置与嵌套调度策略
为保障系统实时性,必须合理分配中断优先级。一般原则是: 输入捕获中断优先级高于PWM更新中断 ,因为丢失一次捕获可能导致时间测量错误,而延迟一次参数更新通常可容忍一个周期。
STM32使用NVIC(嵌套向量中断控制器)支持最多16级抢占优先级(具体取决于内核型号)。以下为配置实例:
// 设置TIM3输入捕获中断(高优先级)
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0); // 抢占优先级0,子优先级0
HAL_NVIC_EnableIRQ(TIM3_IRQn);
// 设置TIM1更新中断(低优先级)
HAL_NVIC_SetPriority(TIM1_UP_TIM10_IRQn, 2, 0); // 抢占优先级2
HAL_NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn);
| 中断源 | IRQn | 抢占优先级 | 应用场景 |
|---|---|---|---|
| TIM3_IRQHandler | TIM3_IRQn | 0 | 高频信号捕捉 |
| TIM1_UP_TIM10_IRQn | TIM1_UP_TIM10_IRQn | 2 | PWM周期同步 |
| TIM2_IRQHandler | TIM2_IRQn | 1 | 编码器测速 |
| USART1_IRQHandler | USART1_IRQn | 3 | 数据通信 |
当高优先级中断到来时,低优先级中断会被挂起,形成中断嵌套。这种机制保证了关键事件的及时响应。
6.4 HAL库与LL库API在中断处理中的实践差异
HAL(Hardware Abstraction Layer)库提供高度封装的接口,便于快速开发;而LL(Low-Layer)库则允许直接操作寄存器,提升性能。
HAL库中断处理特点:
void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)
{
// 自动识别中断源并调用对应回调
// 包含错误处理、标志清除等完整流程
}
优点:
- 代码可移植性强;
- 自动管理标志位;
- 支持多种回调扩展。
缺点:
- 执行路径较长,响应延迟较高;
- 函数调用开销大,不适合超高速信号处理。
LL库中断处理示例:
void TIM3_IRQHandler(void)
{
if (LL_TIM_IsActiveFlag_CC1(TIM3)) {
uint32_t timestamp = LL_TIM_GetCounter(TIM3);
// 直接清除标志
LL_TIM_ClearFlag_CC1(TIM3);
// 处理逻辑
ProcessCapture(timestamp);
}
}
优势:
- 执行速度快,延迟可控制在数个时钟周期内;
- 无额外函数调用开销;
- 更适合高频或抖动敏感的应用。
| 特性 | HAL库 | LL库 |
|---|---|---|
| 开发效率 | 高 | 中 |
| 执行速度 | 慢 | 快 |
| 占用Flash | 大 | 小 |
| 可维护性 | 强 | 弱 |
| 实时性 | 一般 | 高 |
| 调试便利性 | 好 | 依赖经验 |
| 寄存器操作透明度 | 低 | 高 |
在四路输入捕捉+四路PWM输出系统中,推荐采用混合模式:PWM参数更新使用HAL库简化开发,输入捕获使用LL库保证精度。
简介:本文介绍如何在STM32F103ZE微控制器上仅使用两个定时器(TIM)实现四路输入信号捕捉和四路PWM输出控制,无需主从模式配置。通过合理利用定时器的多通道输入捕获和互补输出功能,可在电机驱动、LED控制和电源管理等需要高精度时间同步的场景中实现高效资源利用。文章详细讲解了定时器的工作模式配置、中断处理机制及同步策略,并结合HAL/LL库进行代码实现,确保系统实时性与稳定性。
更多推荐

所有评论(0)