原理分析:为什么PSC和ARR需要协同?

STM32定时器输入捕获测量频率时,通常使用捕获模式记录两次上升沿之间的计数值差。定时器时钟频率为 fCK_CNT = fTIM / (PSC+1),计数周期为 Tcnt = 1/fCK_CNT。捕获到的计数值差 N 对应信号周期 T = N * Tcnt,频率 f = 1/T。

  • 低频分辨率:信号周期长,若PSC较大(计数慢),N值小,量化误差大;应减小PSC,提高计数频率,使N增大。
  • 高频防溢出:信号周期短,若PSC过小(计数快),N可能超过ARR导致溢出;应增大PSC或ARR,使N在合理范围。

ARR决定最大计数值,PSC决定计数步长。固定参数无法同时满足宽频带测量,因此需要动态调整:根据当前测量频率自动切换PSC和ARR,使N始终处于最佳区间(如1000~60000)。

配置步骤

  1. 定时器基础配置:选择输入捕获通道,设置捕获边沿(上升沿),开启捕获中断和更新中断(用于溢出计数)。
  2. 初始参数:根据预期频率范围设定初始PSC和ARR,例如测量1Hz~1MHz,可先设PSC=71(72MHz/72=1MHz计数),ARR=65535。
  3. 捕获中断处理:记录捕获值,计算两次捕获的差值(考虑溢出次数)。
  4. 动态调整逻辑:根据计算出的频率,判断N是否过小(<1000)或过大(>60000),调整PSC和ARR并重新初始化定时器。
  5. 溢出处理:在更新中断中累加溢出次数,用于扩展计数范围。

完整代码示例(基于STM32 HAL库)

// 全局变量
typedef struct {
    uint32_t lastCapture;
    uint32_t overflowCount;
    uint32_t currentPsc;
    uint32_t currentArr;
    float frequency;
} FreqMeas_t;

FreqMeas_t freqMeas = {0, 0, 71, 65535, 0};

// 定时器初始化(动态可重配)
void TIM_Config(uint32_t psc, uint32_t arr) {
    TIM_HandleTypeDef htim2;
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = psc;
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = arr;
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_IC_Init(&htim2);

    TIM_IC_InitTypeDef sConfigIC;
    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);

    HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
    __HAL_TIM_ENABLE_IT(&htim2, TIM_IT_UPDATE);
}

// 捕获中断回调
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
        uint32_t capture = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
        uint32_t diff;
        if (capture >= freqMeas.lastCapture) {
            diff = capture - freqMeas.lastCapture;
        } else {
            diff = (freqMeas.currentArr - freqMeas.lastCapture) + capture + 1;
        }
        diff += freqMeas.overflowCount * (freqMeas.currentArr + 1);
        freqMeas.overflowCount = 0;
        freqMeas.lastCapture = capture;

        if (diff > 0) {
            float tcnt = 1.0f / (72000000.0f / (freqMeas.currentPsc + 1));
            freqMeas.frequency = 1.0f / (diff * tcnt);

            // 动态调整PSC和ARR
            if (diff < 1000) { // 分辨率不足,提高计数频率
                uint32_t newPsc = freqMeas.currentPsc / 2;
                if (newPsc < 1) newPsc = 0;
                uint32_t newArr = 65535;
                if (newPsc != freqMeas.currentPsc) {
                    freqMeas.currentPsc = newPsc;
                    freqMeas.currentArr = newArr;
                    TIM_Config(newPsc, newArr);
                }
            } else if (diff > 60000) { // 接近溢出,降低计数频率
                uint32_t newPsc = freqMeas.currentPsc * 2 + 1;
                if (newPsc > 65535) newPsc = 65535;
                uint32_t newArr = 65535;
                if (newPsc != freqMeas.currentPsc) {
                    freqMeas.currentPsc = newPsc;
                    freqMeas.currentArr = newArr;
                    TIM_Config(newPsc, newArr);
                }
            }
        }
    }
}

// 更新中断回调(溢出计数)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM2) {
        freqMeas.overflowCount++;
    }
}

注意事项

  • 动态重配时机:调整PSC/ARR会复位计数器,建议在捕获到完整周期后立即调整,避免丢失边沿。可先关闭捕获中断,配置后再开启。
  • 溢出处理:ARR较小时溢出频繁,需确保更新中断优先级高于捕获中断,或使用DMA减轻CPU负担。
  • 滤波与消抖:输入信号若有噪声,配置ICFilter(如0x0F)可避免误捕获。
  • 测量范围:受限于定时器位数(16位或32位),极端低频(<1Hz)需配合溢出计数扩展,极端高频(>定时器时钟/2)需外部预分频。
  • 实时性:动态调整算法应尽量简洁,避免在中断中执行浮点运算(可改为定点或查表)。

通过PSC与ARR的动态协同,可在宽频带内保持高分辨率且避免溢出,适用于电机测速、音频频率检测等嵌入式场景。