在嵌入式开发领域,STM32的定时器功能一直是工程师们的得力助手。今天我要分享的是定时器模块中两个极具实用价值的功能:输入捕获和编码器接口。这两个功能在工业控制、机器人、智能家居等领域有着广泛应用。
输入捕获功能主要用于精确测量外部信号的脉宽或频率。想象一下,当你需要测量一个旋转编码器的转速,或者需要精确控制伺服电机时,输入捕获就像是一个高精度的秒表,能够准确记录信号变化的时间点。而编码器接口则是专门为旋转编码器设计的硬件接口,它能自动处理正交编码器的A、B相脉冲,大大简化了位置和速度检测的软件实现。
我曾在多个电机控制项目中应用这些功能,实测发现STM32的硬件实现不仅精度高,而且能显著减轻CPU负担。下面我将结合寄存器配置和实际代码,详细解析这两个功能的实现要点。
输入捕获的核心在于利用定时器的计数器值来标记外部信号边沿发生的时刻。当检测到指定边沿(上升沿、下降沿或两者)时,当前计数器的值会被自动锁存到捕获寄存器中,同时可以触发中断或DMA请求。
以测量PWM占空比为例:
注意:在实际应用中,要考虑计数器溢出的情况。STM32的16位定时器最大计数值为65535,超过这个值就会溢出。解决方法可以是开启溢出中断,或者在32位模式下使用。
以TIM3通道1为例,配置步骤及关键寄存器如下:
c复制RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
c复制GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
c复制TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 0xFFFF; // 自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler = 72-1; // 72分频,1MHz计数频率
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
c复制TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; // 上升沿捕获
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // 直接映射到TI1
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 不分频
TIM_ICInitStructure.TIM_ICFilter = 0x0; // 不滤波
TIM_ICInit(TIM3, &TIM_ICInitStructure);
c复制TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE);
NVIC_EnableIRQ(TIM3_IRQn);
c复制TIM_Cmd(TIM3, ENABLE);
在中断服务程序中,我们需要处理捕获事件并计算相关参数:
c复制void TIM3_IRQHandler(void) {
if(TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET) {
static uint16_t lastCapture = 0;
uint16_t currentCapture = TIM_GetCapture1(TIM3);
// 计算两次捕获之间的差值
uint16_t pulseWidth = currentCapture - lastCapture;
lastCapture = currentCapture;
// 这里可以添加你的处理逻辑,比如计算频率、占空比等
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);
}
}
旋转编码器通常输出两路正交信号(A相和B相),STM32的编码器接口模式可以自动解析这两路信号,实现位置和方向的检测。硬件会自动根据两路信号的相位关系判断旋转方向,并相应地增加或减少计数器值。
编码器接口有三种工作模式:
最常用的是模式3,因为它可以提供最高的分辨率(4倍频),即每个编码器周期会产生4个计数。
以下是一个完整的编码器接口配置示例:
c复制GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
c复制TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 0xFFFF; // 16位最大值
TIM_TimeBaseStructure.TIM_Prescaler = 0; // 不分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
c复制TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x0;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
c复制TIM_SetCounter(TIM2, 0); // 计数器清零
TIM_Cmd(TIM2, ENABLE);
读取编码器位置非常简单,直接读取计数器值即可:
c复制int16_t getEncoderPosition(void) {
return (int16_t)TIM_GetCounter(TIM2);
}
在实际应用中,我们还需要处理计数器溢出和计算相对位置。下面是一个更完整的示例:
c复制int32_t totalPosition = 0;
int16_t lastPosition = 0;
void updateEncoderPosition(void) {
int16_t currentPosition = TIM_GetCounter(TIM2);
int16_t delta = currentPosition - lastPosition;
// 处理计数器溢出
if(delta > 0x7FFF) {
delta -= 0xFFFF;
} else if(delta < -0x7FFF) {
delta += 0xFFFF;
}
totalPosition += delta;
lastPosition = currentPosition;
}
时钟源选择:使用更高精度的时钟源可以显著提高测量精度。对于72MHz主频的STM32,如果定时器直接使用系统时钟,理论最小分辨率为13.89ns(1/72MHz)。
预分频设置:根据被测信号频率合理设置预分频值。对于高频信号,使用较小的预分频甚至不分频;对于低频信号,可以适当增大预分频值以扩展测量范围。
数字滤波:STM32的输入捕获通道提供了可配置的数字滤波器(TIM_ICFilter参数),可以有效消除信号抖动。滤波参数需要根据信号特性调整,一般设置为2-8个时钟周期。
多次测量平均:对于周期性信号,可以采用多次测量取平均的方法提高精度。例如测量PWM频率时,可以捕获多个周期然后计算平均值。
计数方向相反:如果发现编码器旋转方向与计数方向相反,可以交换A、B相的接线,或者在软件中取反计数值。
计数丢失:如果发现计数不准确,首先检查:
高速旋转时计数错误:当编码器转速较高时,可能出现计数丢失。解决方法包括:
位置累积误差:长时间运行后,软件计算的位置可能出现累积误差。解决方法包括:
使用DMA减少CPU开销:对于高频信号测量,可以配置DMA将捕获值直接传输到内存,避免频繁中断。
定时器级联:对于需要超长周期测量的应用,可以将两个定时器级联使用,一个作为预分频器,另一个作为主计数器。
互补PWM与编码器结合:在电机控制应用中,可以将编码器接口与定时器的PWM输出功能结合使用,实现闭环控制。
低功耗考虑:在电池供电应用中,可以配置定时器在捕获事件后自动停止,减少功耗。
利用STM32的多个输入捕获通道,可以实现多通道频率计。下面是一个双通道频率计的配置示例:
c复制// 通道1配置(PA6)
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x04;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
// 通道2配置(PA7)
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
// 使能两个通道的中断
TIM_ITConfig(TIM3, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);
在中断处理函数中分别处理两个通道的数据:
c复制void TIM3_IRQHandler(void) {
if(TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET) {
// 处理通道1数据
static uint16_t lastCapture1 = 0;
uint16_t currentCapture1 = TIM_GetCapture1(TIM3);
uint16_t period1 = currentCapture1 - lastCapture1;
lastCapture1 = currentCapture1;
// 计算频率:freq1 = 1MHz / period1
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);
}
if(TIM_GetITStatus(TIM3, TIM_IT_CC2) != RESET) {
// 处理通道2数据
static uint16_t lastCapture2 = 0;
uint16_t currentCapture2 = TIM_GetCapture2(TIM3);
uint16_t period2 = currentCapture2 - lastCapture2;
lastCapture2 = currentCapture2;
// 计算频率:freq2 = 1MHz / period2
TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);
}
}
结合编码器接口和输入捕获功能,可以实现高精度的位置检测系统。例如,使用编码器接口获取大致位置,同时使用输入捕获测量编码器Z相信号(零点信号),实现绝对位置校准。
配置步骤:
c复制// TIM3(Z相)中断处理
void TIM3_IRQHandler(void) {
if(TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET) {
// 检测到Z相信号,重置位置计数器
TIM_SetCounter(TIM2, 0);
totalPosition = 0;
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);
}
}
这种组合应用可以同时获得编码器的高分辨率和Z相的绝对位置参考,特别适用于需要精确定位的场合,如CNC机床、3D打印机等。