电机控制系统中,精确测量转速是构建闭环反馈的基础。传统软件计数方式需要频繁中断响应,不仅占用CPU资源,还容易因处理延迟导致数据丢失。STM32系列微控制器内置的定时器编码器接口模式,将这一过程完全硬件化——信号捕获、方向判断、计数累加全部由外设自动完成,开发者只需定期读取寄存器值即可获得精准的转速数据。这种"配置即用"的特性特别适合智能小车、机械臂等实时性要求高的场景。
光电编码器通过光栅盘产生两路相位差90°的方波信号(A/B相)。传统软件测速需要将这两路信号接入外部中断引脚,在中断服务函数中手动判断方向和累加计数。这种方式存在三个明显缺陷:
STM32的定时器编码器模式通过硬件逻辑完美解决了这些问题:
| 对比维度 | 软件计数方案 | 硬件编码器模式 |
|---|---|---|
| CPU占用 | 高频中断占用大量资源 | 零中断自动计数 |
| 最高测速频率 | 受中断响应时间限制 | 可达定时器时钟频率的1/4 |
| 方向检测 | 需软件比较两信号相位 | 硬件自动判断方向 |
| 抗抖动能力 | 需软件滤波 | 可配置数字滤波器 |
| 四倍频支持 | 需四倍中断频率 | 硬件自动实现四倍频 |
硬件编码器的工作原理:定时器将A/B相信号接入TI1/TI2引脚,内部边沿检测电路自动根据两个信号的相对相位关系确定计数方向(向上/向下),同时支持对信号上升沿和下降沿都进行计数(即四倍频模式)。这种设计使得在1000线编码器+四倍频下,每转可产生4000个计数脉冲,大幅提升分辨率。
我们以STM32F103C8T6的TIM2为例,演示完整的配置流程。打开CubeMX后按照以下步骤操作:
定时器模式选择:
TIM2配置界面,将Combined Channels设为Encoder ModeEncoder Mode选择TI1 and TI2(同时使用两路信号)滤波器配置:
c复制/* 根据信号质量设置数字滤波器,典型值:
* 无抖动:0x0
* 轻微抖动:0x1~0x3
* 强干扰:0xF(最大滤波)*/
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
htim2.EncoderMode = TIM_ENCODERMODE_TI12;
htim2.IC1Filter = 0xF; // TI1输入滤波器
htim2.IC2Filter = 0xF; // TI2输入滤波器
引脚配置检查:
定时器参数设置:
Counter Period(ARR)设为65535(16位定时器最大值)Prescaler保持为0(不分频)auto-reload preload设为Disable生成代码后,只需两行代码即可启动编码器接口:
c复制HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
HAL_TIM_Base_Start(&htim2); // 启动基准时钟
注意:编码器模式下预分频器(PSC)和计数方向(CR1.DIR)由硬件自动管理,软件修改无效。ARR值决定计数范围,溢出时会自动翻转。
读取编码器数据时,需要处理计数器溢出问题。16位定时器的计数范围为0-65535,当电机持续单向旋转时,计数器可能发生多次溢出。我们提供三种实用方案:
方案1:溢出中断+全局变量(适合低速场景)
c复制volatile int32_t totalCount = 0;
// 在定时器溢出中断中
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if(htim->Instance == TIM2) {
if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE)) {
totalCount += 65536 * (htim->Instance->CR1 & TIM_CR1_DIR ? -1 : 1);
__HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_UPDATE);
}
}
}
// 获取累计计数值
int32_t getTotalCount() {
return totalCount + __HAL_TIM_GET_COUNTER(&htim2);
}
方案2:定期采样差值法(推荐平衡方案)
c复制int16_t lastCount = 0;
uint32_t lastTime = 0;
float getRPM(uint16_t sampleTimeMs) {
int16_t currentCount = __HAL_TIM_GET_COUNTER(&htim2);
int32_t delta = currentCount - lastCount;
// 处理计数器溢出
if(delta > 32767) delta -= 65536;
else if(delta < -32767) delta += 65536;
lastCount = currentCount;
// 假设编码器为1000线,四倍频后每转4000脉冲
return (delta * 60000.0f) / (4000 * sampleTimeMs);
}
方案3:32位扩展计数(高精度方案)
c复制typedef struct {
TIM_HandleTypeDef *htim;
int32_t overflow;
uint16_t lastCnt;
} Encoder_TypeDef;
void updateEncoder(Encoder_TypeDef *enc) {
uint16_t cnt = __HAL_TIM_GET_COUNTER(enc->htim);
int16_t delta = cnt - enc->lastCnt;
enc->overflow += delta; // 自动处理16位溢出
enc->lastCnt = cnt;
}
// 使用示例
Encoder_TypeDef enc2 = {&htim2, 0, 0};
updateEncoder(&enc2);
int32_t totalCount = enc2.overflow;
对于不同精度要求的场景,可参考以下配置建议:
| 应用场景 | 推荐方案 | 采样周期 | 滤波器设置 | 备注 |
|---|---|---|---|---|
| 低速高精度 | 方案3 | 10-100ms | 0x1-0x3 | 如机械臂关节控制 |
| 中速平衡型 | 方案2 | 5-20ms | 0x3-0x7 | 智能小车常用配置 |
| 高速低延迟 | 方案1+DMA | 1-5ms | 0x7-0xF | 需配合高速编码器使用 |
在实际项目中,编码器测速往往需要与其他功能协同工作。以下是三个典型场景的优化建议:
场景1:PWM驱动+编码器闭环控制
c复制// 配置TIM1输出PWM,TIM2作为编码器接口
void Motor_Control(int32_t targetSpeed) {
static int32_t integral = 0;
float currentSpeed = getRPM(10); // 10ms采样周期
// 简易PI控制器
float error = targetSpeed - currentSpeed;
integral += error * 0.1f; // Ki=0.1
int32_t pwm = error * 0.5f + integral; // Kp=0.5
// 限制PWM输出范围
pwm = (pwm > 1000) ? 1000 : (pwm < -1000) ? -1000 : pwm;
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, abs(pwm));
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, pwm > 0 ? GPIO_PIN_SET : GPIO_PIN_RESET);
}
场景2:多编码器+蓝牙传输
c复制// 使用TIM2+TIM4同时采集两个电机数据
void sendMotorData() {
uint8_t buf[20];
float speed1 = getRPM(TIM2, 20);
float speed2 = getRPM(TIM4, 20);
int len = sprintf(buf, "M1:%.1f M2:%.1f\n", speed1, speed2);
HAL_UART_Transmit(&huart1, buf, len, 10);
}
// 在main循环中定期调用
while(1) {
sendMotorData();
HAL_Delay(200);
}
场景3:抗干扰优化措施
硬件层面:
软件层面:
c复制// 动态调整滤波器参数
void adjustFilter(uint8_t level) {
TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_IC1F) | (level << 4);
TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_IC2F) | (level << 12);
}
// 异常值过滤
#define SPEED_THRESHOLD 3000 // 最大合理转速(RPM)
float safeGetRPM() {
float speed = getRPM(10);
return (fabs(speed) > SPEED_THRESHOLD) ? 0 : speed;
}
对于需要更高精度的场合,可以启用定时器的输入捕获功能辅助校准:
c复制// 配置TIM3通道1捕获编码器Z相信号
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
static uint32_t lastCap = 0;
uint32_t cap = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
// 每转脉冲数校准
pulsesPerRev = __HAL_TIM_GET_COUNTER(&htim2) - lastCap;
lastCap = __HAL_TIM_GET_COUNTER(&htim2);
}
}