STM32正交编码器与PID电机闭环控制实战
1. 项目背景与核心需求在工业自动化和机器人控制领域精确的电机速度控制是基础需求。传统开环控制方式存在明显缺陷无法感知实际转速、难以应对负载变化、缺乏堵转保护机制。这正是正交编码器与STM32结合的典型应用场景。正交编码器Quadrature Encoder通过两路相位差90°的脉冲信号A相和B相不仅能测量转速还能判断旋转方向。STM32系列微控制器内置专用编码器接口TIMx_ENCODER模式可直接处理这种信号无需额外解码电路。实际工程中我曾遇到因缺乏编码器反馈导致电机失控的案例一台AGV小车在斜坡上因负载增加而失速由于开环控制无法检测速度下降最终导致任务失败。这促使我们全面转向闭环控制方案。2. 硬件系统搭建2.1 关键器件选型主控芯片STM32F103C8T672MHz Cortex-M3内核含4个通用定时器电机驱动模块TB6612FNG双H桥最大1.2A持续电流编码器欧姆龙E6B2-CWZ6C500线ABZ三相输出电源系统12V/2A直流电源电机供电 AMS1117-3.3MCU供电2.2 电路连接要点graph LR A[编码器A相] --|接10k上拉| B(TIM3_CH1/PA6) C[编码器B相] --|接10k上拉| D(TIM3_CH2/PA7) E[TB6612 PWMA] -- F(TIM1_CH1/PA8) G[STM32] --|GPIO控制| H(TB6612 AIN1/AIN2)实际接线时需注意编码器信号线需加10kΩ上拉电阻部分编码器内置可省略TB6612的VM电机电源与VCC逻辑电源必须隔离电机外壳与MCU共地但大电流回路需单独走线3. STM32编码器接口配置3.1 定时器初始化以TIM3为例void Encoder_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // 时基单元配置实际时钟由编码器提供 TIM_TimeBaseStructInit(TIM_TimeBaseStructure); TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_Period 65535; // 16位计数器最大值 TIM_TimeBaseInit(TIM3, TIM_TimeBaseStructure); // 编码器接口模式配置 TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, // 双通道计数模式 TIM_ICPolarity_Rising, // 捕获上升沿 TIM_ICPolarity_Rising); TIM_ICStructInit(TIM_ICInitStructure); TIM_ICInitStructure.TIM_ICFilter 6; // 设置输入滤波器抗干扰 TIM_ICInit(TIM3, TIM_ICInitStructure); TIM_Cmd(TIM3, ENABLE); }3.2 速度计算算法编码器每转产生N个脉冲N4×编码器线数采样周期T内计数值为Δcnt则转速RPM计算公式RPM (Δcnt × 60) / (N × T)具体实现需处理计数器溢出16位无符号数int16_t Get_Speed(uint32_t interval_ms) { static uint16_t last_cnt 0; uint16_t curr_cnt TIM3-CNT; int16_t delta (int16_t)(curr_cnt - last_cnt); last_cnt curr_cnt; // 500线编码器×4倍频 2000脉冲/转 float rpm (delta * 60.0f) / (2000 * interval_ms/1000.0f); return (int16_t)rpm; }4. 电机驱动与PID控制4.1 PWM驱动配置void PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 使能TIM1时钟和GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置PA8为TIM1_CH1输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 时基单元配置72MHz主频 TIM_TimeBaseStructure.TIM_Period 999; // PWM频率72MHz/(9991)72kHz TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure); // PWM模式配置 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; // 初始占空比0% TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM1, TIM_OCInitStructure); TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); }4.2 增量式PID实现typedef struct { float Kp, Ki, Kd; float err, last_err, integral; float output_max, output_min; } PID_Controller; void PID_Init(PID_Controller* pid, float kp, float ki, float kd, float max, float min) { pid-Kp kp; pid-Ki ki; pid-Kd kd; pid-output_max max; pid-output_min min; pid-err pid-last_err pid-integral 0; } float PID_Update(PID_Controller* pid, float target, float feedback, float dt) { pid-err target - feedback; pid-integral pid-err * dt; // 抗积分饱和 if(pid-integral pid-output_max/pid-Ki) pid-integral pid-output_max/pid-Ki; else if(pid-integral pid-output_min/pid-Ki) pid-integral pid-output_min/pid-Ki; float derivative (pid-err - pid-last_err) / dt; pid-last_err pid-err; float output pid-Kp * pid-err pid-Ki * pid-integral pid-Kd * derivative; // 输出限幅 if(output pid-output_max) output pid-output_max; if(output pid-output_min) output pid-output_min; return output; }5. 系统整合与调试技巧5.1 主控制循环示例int main(void) { SystemInit(); Encoder_Config(); PWM_Init(); PID_Controller speed_pid; PID_Init(speed_pid, 0.8f, 0.05f, 0.01f, 999, 0); uint32_t last_time SysTick-VAL; while(1) { uint32_t now SysTick-VAL; uint32_t elapsed (last_time - now) % 72000; // 1ms72MHz if(elapsed 10) { // 10ms控制周期 int16_t rpm Get_Speed(10); float pwm PID_Update(speed_pid, 300.0f, rpm, 0.01f); TIM1-CCR1 (uint16_t)pwm; last_time now; } } }5.2 常见问题排查编码器计数异常现象计数器值不变化或跳变检查用示波器观察AB相信号质量调整TIM_ICInitStructure.TIM_ICFilter值建议4-8确认GPIO模式设置为浮空输入或上拉输入电机抖动可能原因PID参数过激进先尝试Kp0.5,Ki0,Kd0PWM频率过低建议≥20kHz避免可闻噪声电源功率不足观察电机启动时电压跌落方向判断错误测试方法手动旋转电机观察CNT增减方向若方向相反交换AB相接线或修改TIM_EncoderInterfaceConfig模式调试时的一个实用技巧先断开电机用手旋转编码器轴观察计数器变化确认硬件层正常工作后再接入电机驱动。我曾因此节省了数小时排查时间。