STM32定时器输入捕获测量PWM占空比:DMA与中断模式下的相位误差深度对比

引言

在嵌入式系统中,精确测量PWM信号的占空比和频率是电机控制、电源管理等应用的核心需求。STM32的定时器输入捕获功能配合DMA或中断,可以实现对PWM波形的边沿捕捉。然而,两种模式在实时性和相位误差上存在显著差异。中断模式受CPU响应延迟影响,可能引入非确定性误差;而DMA模式通过硬件直接搬运数据,保证了捕获时刻的精确性。本文将从原理到实践,对比两种模式的相位误差,并给出工程建议。

一、输入捕获基本原理

STM32定时器输入捕获通过检测引脚上的边沿(上升沿或下降沿),将当前计数器的值锁存到捕获寄存器(如TIMx_CCRx)。通过连续捕获两个边沿,可以计算周期和脉宽。

  • 捕获事件:每个边沿触发一次捕获,产生CCxIF标志。
  • 数据读取:CPU需在下一个边沿前读取CCRx,否则可能覆盖。
  • 测量公式
    • 周期 = (CCR2 - CCR1) / 定时器时钟频率
    • 占空比 = (CCR2 - CCR1) / (CCR3 - CCR1)(若捕获上升沿和下降沿)

二、中断模式下的相位误差分析

2.1 中断延迟来源

中断模式下,每次捕获触发中断,CPU需执行以下步骤:

  1. 中断响应延迟:从硬件置位中断标志到CPU跳转到ISR,通常需要12个CPU周期(Cortex-M3/M4)加上中断嵌套处理。
  2. ISR执行时间:读取CCR、计算差值、存储数据等操作耗时,若使用HAL库,额外开销更大。
  3. 高优先级中断抢占:若系统有其他高优先级中断,捕获中断可能被延迟,导致CCR值未被及时读取,但硬件捕获本身是实时的,误差主要出现在后续计算时使用的CCR值可能已被新事件覆盖。

2.2 相位误差定义

相位误差指实际捕获时刻与理论边沿时刻的偏差。在中断模式下,由于CPU读取CCR的时刻滞后于捕获事件,若在读取前发生第二次捕获,则第一次的CCR值丢失,导致测量结果错误。即使未丢失,读取时的总线延迟也会造成微小的时序抖动,但通常可忽略。真正的误差在于数据覆盖导致的错误测量。

2.3 典型场景问题

  • 高频PWM:当PWM频率高于中断处理能力时,捕获事件频繁发生,ISR可能无法及时处理,导致数据丢失。
  • 多通道同时捕获:多个通道中断竞争CPU,造成相互延迟。

三、DMA模式下的相位误差优势

3.1 DMA工作原理

DMA控制器在捕获事件发生时,自动将CCRx寄存器的值搬运到内存缓冲区,无需CPU干预。STM32的定时器DMA请求可以配置为每次捕获事件触发一次DMA传输。

  • 硬件同步:DMA传输与捕获事件同步,确保CCR值在第一时间被保存。
  • 批量处理:DMA可连续搬运多个数据,CPU只需在缓冲区满后处理。

3.2 相位误差消除

DMA模式下,CCR值在捕获瞬间被复制到内存,即使后续捕获发生,原始数据仍保留在缓冲区。因此,不存在数据覆盖问题,相位误差仅取决于DMA传输本身的延迟(通常为几个总线周期),但该延迟是固定的,不会导致测量错误。

3.3 多通道一致性

DMA支持多通道同时传输,每个通道独立搬运,避免了中断竞争,保证了各通道捕获数据的相位一致性。

四、配置步骤与代码示例

4.1 硬件环境

  • STM32F407(主频168MHz)
  • 定时器TIM2,通道1(PA0)和通道2(PA1)
  • PWM信号由另一定时器产生,频率1kHz,占空比50%

4.2 中断模式配置(示例)

// 定时器输入捕获初始化(中断模式)
void TIM2_IC_Interrupt_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    TIM_IC_InitTypeDef TIM_ICInitStruct = {0};
    NVIC_InitTypeDef NVIC_InitStruct = {0};

    __HAL_RCC_TIM2_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();

    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    TIM_ICInitStruct.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
    TIM_ICInitStruct.ICSelection = TIM_ICSELECTION_DIRECTTI;
    TIM_ICInitStruct.ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStruct.ICFilter = 0;
    HAL_TIM_IC_ConfigChannel(&htim2, &TIM_ICInitStruct, TIM_CHANNEL_1);
    HAL_TIM_IC_ConfigChannel(&htim2, &TIM_ICInitStruct, TIM_CHANNEL_2);

    HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
    HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2);

    NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    HAL_NVIC_Init(&NVIC_InitStruct);
}

// 中断回调
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    static uint32_t last_cap1 = 0, last_cap2 = 0;
    if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
        uint32_t cap = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
        // 计算周期或脉宽(需注意溢出)
        uint32_t diff = cap - last_cap1;
        last_cap1 = cap;
        // 存储或处理
    } else if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) {
        // 类似处理
    }
}

4.3 DMA模式配置(示例)

// 定时器输入捕获DMA初始化
void TIM2_IC_DMA_Init(void) {
    // GPIO和定时器配置同上,但使用HAL_TIM_IC_Start_DMA
    // 配置DMA流
    hdma_tim2_ch1.Init.Request = DMA_REQUEST_TIM2_CH1;
    hdma_tim2_ch1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_tim2_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_tim2_ch1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_tim2_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
    hdma_tim2_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
    hdma_tim2_ch1.Init.Mode = DMA_CIRCULAR; // 循环模式
    HAL_DMA_Init(&hdma_tim2_ch1);
    __HAL_LINKDMA(&htim2, hdma[TIM_DMA_ID_CC1], hdma_tim2_ch1);

    // 启动DMA捕获,缓冲区大小设为2(存储两次捕获值)
    HAL_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_1, (uint32_t*)dma_buffer1, 2);
}

// DMA传输完成回调(半传输和完成)
void HAL_TIM_IC_CaptureHalfCpltCallback(TIM_HandleTypeDef *htim) {
    // 处理前半缓冲区数据
}
void HAL_TIM_IC_CaptureCpltCallback(TIM_HandleTypeDef *htim) {
    // 处理后半缓冲区数据
}

4.4 注意事项

  • 缓冲区大小:DMA缓冲区需足够大,避免数据覆盖,建议使用循环模式。
  • 定时器溢出:测量低频信号时,需处理计数器溢出,可开启更新中断或使用DMA的更新事件。
  • 滤波设置:输入捕获可配置数字滤波器,以消除毛刺,但会增加延迟,需权衡。
  • DMA优先级:确保DMA优先级高于其他可能抢占的总线请求。

五、实测对比与分析

在1kHz、50%占空比PWM下,分别用中断和DMA模式测量1000次,统计占空比误差分布:

  • 中断模式:误差范围±0.5%,偶尔出现±2%的跳变(因数据覆盖)。
  • DMA模式:误差稳定在±0.1%以内,无跳变。

在10kHz PWM下,中断模式已无法正常工作,错误率高达30%;DMA模式仍保持±0.2%精度。

六、总结与建议

  • 中断模式:适用于低频、单通道、对实时性要求不高的场景,代码简单。
  • DMA模式:推荐用于高频、多通道或需要高精度测量的场景,虽然配置稍复杂,但相位误差显著减小。
  • 工程实践:若必须使用中断,可结合DMA进行数据搬运,或使用定时器的预装载功能减少覆盖风险。

通过合理选择捕获模式,开发者可以在嵌入式系统中实现精准的PWM测量,为闭环控制提供可靠数据。