一、为什么需要从模式复位?

测量PWM占空比时,常规做法是:

  • 捕获上升沿,记录CCR1,手动清零CNT;
  • 捕获下降沿,记录CCR2,计算占空比 = (CCR2 - CCR1) / 周期。

但手动清零CNT存在两个问题:

  • 时序误差:软件写CNT与捕获事件之间存在延迟,导致周期测量偏差;
  • 代码冗余:每次捕获中断都要执行清零操作,增加中断负载。

STM32定时器的从模式复位功能,可在捕获到指定边沿时硬件自动将CNT清零,彻底消除软件延迟。

二、原理讲解:从模式复位如何工作

STM32定时器从模式控制器(Slave Mode Controller)支持多种触发源,其中复位模式(Reset Mode)会在选定的触发信号(如TI1FP1)出现上升沿时,自动将计数器CNT重置为0。

关键配置:

  • 触发源:选择TI1FP1(通道1的滤波后信号);
  • 从模式:选择Reset Mode;
  • 输入捕获通道1:配置为上升沿捕获,并映射到TI1;
  • 输入捕获通道2:配置为下降沿捕获,映射到TI1(间接模式)。

这样,每次上升沿到来时:

  1. 硬件自动清零CNT;
  2. 同时触发CCR1捕获(值为0或极小值);
  3. 下降沿时CCR2捕获高电平时间。

占空比 = CCR2 / (CCR1_next - CCR1) ,但更简单的方法是:由于CNT在上升沿被清零,CCR2直接就是高电平时间,而周期可通过两次上升沿的CCR1差值获得(或使用PWM输入模式)。

三、CubeMX配置步骤

以STM32F103C8T6,TIM2通道1(PA0)为例:

  1. 时钟配置:TIM2时钟72MHz,预分频设为71,计数频率1MHz(1us分辨率)。
  2. 通道1:输入捕获直接模式,上升沿触发,映射到TI1。
  3. 通道2:输入捕获间接模式,下降沿触发,映射到TI1。
  4. 从模式控制器:Trigger Source选择TI1FP1,Slave Mode选择Reset Mode。
  5. NVIC:使能TIM2全局中断。

四、完整代码示例

// tim.c 中初始化函数(CubeMX生成后补充)
void MX_TIM2_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_IC_InitTypeDef sConfigIC = {0};
  TIM_SlaveConfigTypeDef sSlaveConfig = {0};

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 71;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 0xFFFF;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  HAL_TIM_IC_Init(&htim2);

  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);

  // 通道1:上升沿捕获,直接模式
  sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
  sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
  sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
  sConfigIC.ICFilter = 0;
  HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);

  // 通道2:下降沿捕获,间接模式(映射到TI1)
  sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
  sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI;
  HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_2);

  // 从模式复位:TI1FP1触发
  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET;
  sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;
  sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
  sSlaveConfig.TriggerFilter = 0;
  HAL_TIM_SlaveConfigSynchro(&htim2, &sSlaveConfig);

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

// 捕获中断回调
volatile uint32_t high_time = 0, period = 0;
volatile float duty = 0.0f;

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == TIM2) {
    if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
      // 上升沿:CNT已被硬件清零,读取CCR1(通常为0)
      uint32_t ccr1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
      // 周期 = 本次CCR1 + 上次高电平时间(因为CNT在上升沿清零,下降沿到下次上升沿的计数为周期-高电平)
      // 更简单:使用两次上升沿的CCR1差值,但需保存上次值
      static uint32_t last_ccr1 = 0;
      if (last_ccr1 != 0) {
        period = ccr1 - last_ccr1; // 注意:由于CNT在上升沿清零,ccr1通常为0,此方法需结合溢出处理
      }
      last_ccr1 = ccr1;
    } else if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) {
      // 下降沿:CCR2即为高电平时间
      high_time = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
      if (period != 0) {
        duty = (float)high_time / period * 100.0f;
      }
    }
  }
}

更简洁的方案:使用PWM输入模式(PWM Input Mode),只需一个通道即可同时获得周期和高电平时间。配置时将从模式设为Reset,通道1上升沿、通道2下降沿,CCR1自动为周期,CCR2为高电平时间。

五、注意事项

  • 滤波器设置:根据信号频率调整ICFilter,避免噪声误触发复位。
  • 计数器溢出:若PWM周期超过65535(16位定时器),需开启溢出中断并累计溢出次数。
  • 从模式复位与预分频:复位信号会清零CNT,但不会影响预分频器,确保预分频值固定。
  • 中断优先级:捕获中断优先级应高于其他低速中断,减少响应延迟。
  • 调试技巧:可用示波器观察CNT寄存器,确认上升沿时是否归零。

六、总结

利用从模式复位,STM32定时器可在硬件层面自动清零计数器,消除软件延迟,提高PWM占空比测量精度。结合PWM输入模式,代码可进一步简化。掌握这一技巧,对电机控制、电源管理等嵌入式应用大有裨益。