原理分析:为什么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)。
配置步骤
- 定时器基础配置:选择输入捕获通道,设置捕获边沿(上升沿),开启捕获中断和更新中断(用于溢出计数)。
- 初始参数:根据预期频率范围设定初始PSC和ARR,例如测量1Hz~1MHz,可先设PSC=71(72MHz/72=1MHz计数),ARR=65535。
- 捕获中断处理:记录捕获值,计算两次捕获的差值(考虑溢出次数)。
- 动态调整逻辑:根据计算出的频率,判断N是否过小(<1000)或过大(>60000),调整PSC和ARR并重新初始化定时器。
- 溢出处理:在更新中断中累加溢出次数,用于扩展计数范围。
完整代码示例(基于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的动态协同,可在宽频带内保持高分辨率且避免溢出,适用于电机测速、音频频率检测等嵌入式场景。
暂无评论,来抢沙发~