增量式编码器就像工业控制领域的"眼睛",它能将机械运动转化为电信号。而STM32作为嵌入式开发的明星选手,其强大的外部中断功能正是处理这类脉冲信号的绝佳搭档。我曾在多个电机控制项目中采用这种组合,实测下来既稳定又精准。
这种方案特别适合需要精确位置控制的场景,比如数控机床、机器人关节、自动化生产线等。对于刚接触硬件的开发者来说,理解AB相信号的处理逻辑可能有些门槛,但跟着我的步骤操作,你很快就能掌握这个实用技能。
增量式编码器通常输出三组信号:A相、B相和Z相(零位信号)。AB两相信号相位差90度,这个特性正是判断方向的关键。我建议使用带屏蔽的双绞线连接,可以显著降低干扰。具体接线时:
注意:有些工业编码器输出是5V电平,需要加电平转换电路或使用开漏输出模式
当编码器旋转时,AB相会产生正交方波。正转时A相领先B相90度,反转时则相反。通过示波器抓取的典型波形如下:
| 旋转方向 | A相波形 | B相波形 |
|---|---|---|
| 顺时针 | 领先90° | 滞后90° |
| 逆时针 | 滞后90° | 领先90° |
这种相位关系是方向判断的基础,我在调试时总会先用示波器确认信号质量,这个习惯帮我省去了很多后期调试时间。
首先要用STM32CubeMX配置GPIO,或者直接写寄存器。以STM32F4系列为例:
c复制// 初始化A相引脚为外部中断
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; // 上升沿触发
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// B相配置为普通输入
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
中断优先级配置很关键,特别是在实时性要求高的场合:
c复制HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
我一般会把编码器中断设为最高优先级,避免丢失脉冲。但也要注意不要阻塞其他重要中断。
在中断服务函数中,我们需要完成三件事:方向判断、计数更新和消抖处理。基本框架如下:
c复制void EXTI0_IRQHandler(void) {
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
// 添加用户代码
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if(GPIO_Pin == GPIO_PIN_0) {
uint8_t b_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1);
if(b_state == GPIO_PIN_RESET) {
counter++; // 正转
} else {
counter--; // 反转
}
}
}
机械编码器普遍存在抖动问题,我的经验是采用"两次采样法":
c复制#define DEBOUNCE_TIME 5 // 5ms消抖时间
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
static uint32_t last_time = 0;
uint32_t current = HAL_GetTick();
if(current - last_time > DEBOUNCE_TIME) {
// 实际处理逻辑
last_time = current;
}
}
对于高速旋转的场景,还可以采用硬件滤波,在信号输入端并联100pF电容。
通过同时捕获AB相的上升沿和下降沿,可以将分辨率提高4倍:
c复制// 在中断中判断所有边沿
if(GPIO_Pin == GPIO_PIN_0 || GPIO_Pin == GPIO_PIN_1) {
uint8_t a_now = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
uint8_t b_now = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1);
static uint8_t a_last = 0, b_last = 0;
if(a_now != a_last || b_now != b_last) {
// 方向判断逻辑
a_last = a_now;
b_last = b_now;
}
}
结合定时器可以计算转速,我常用的方法是"脉冲间隔法":
c复制uint32_t last_pulse_time = 0;
float speed_rpm = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
uint32_t now = TIM2->CNT; // 使用定时器获取精确时间
uint32_t interval = now - last_pulse_time;
if(interval > 0) {
speed_rpm = 60.0f / (interval * PULSE_PER_REV * TIMER_CLOCK);
}
last_pulse_time = now;
// ...原有计数逻辑
}
在工业现场,电磁干扰是常见问题。我总结了几点防护措施:
编码器安装也很有讲究,我曾经遇到因为机械偏心导致计数不准的情况。后来改用柔性联轴器,并确保编码器与转轴严格同轴,问题迎刃而解。
对于长距离传输,建议改用差分信号编码器(如RS422接口),抗干扰能力更强。STM32的USART接口配合MAX490芯片就能实现可靠接收。
当编码器转速较高时,频繁中断可能导致CPU负载过重。这时可以采用"DMA+定时器"的硬件计数方案:
c复制// 配置TIM2为编码器模式
TIM_Encoder_InitTypeDef encoder = {0};
encoder.EncoderMode = TIM_ENCODERMODE_TI12;
encoder.IC1Polarity = TIM_ICPOLARITY_RISING;
encoder.IC1Selection = TIM_ICSELECTION_DIRECTTI;
encoder.IC1Prescaler = TIM_ICPSC_DIV1;
encoder.IC1Filter = 0x0;
// 类似配置IC2
HAL_TIM_Encoder_Init(&htim2, &encoder);
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
调试时,我习惯用STM32的SWO输出实时数据,配合J-Scope可视化工具,可以直观观察脉冲变化趋势。遇到异常计数时,首先要检查: