STM32F401平台闭环步进驱动方案,支持开环模式兼容42,57,60 86两相开环闭环步进电机,提供原理图+PCB+源代码

最近在搞步进电机驱动的兄弟看过来!今天分享个基于STM32F401的骚操作——同时支持开环和闭环控制的步进驱动方案。这玩意儿实测能扛42/57/60/86两相步进电机,闭环模式下堵转直接硬刚,开环模式还能向下兼容老设备,实测效果比某些商业驱动器还要顶。

硬件设计上核心是电流采样电路,这里用了双运放差分方案。直接上代码看ADC配置:

void ADC_Config(void) {
    ADC_ChannelConfTypeDef sConfig = {0};
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.ScanConvMode = ENABLE;
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    HAL_ADC_Start(&hadc1);  // 关键在这启动连续转换
}

这个配置实现的是连续扫描模式,实测采样率能跑到500ksps。注意这里没开外部触发,直接靠定时器中断触发采样可能会更精准,不过对于步进电机控制来说这个采样率已经够用了。

驱动部分用了全桥MOSFET,PWM生成是关键。看TIM1的初始化:

void MX_TIM1_Init(void) {
    htim1.Instance = TIM1;
    htim1.Init.Prescaler = 0;
    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim1.Init.Period = 1680-1;  // 主频168MHz时对应100kHz PWM
    htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);  // 两路互补输出自动开启
}

这里有个坑要注意:ARR寄存器设置的是PWM周期,CCR才是占空比。调试时发现如果占空比超过95%会导致MOS管发热异常,后来在死区时间配置里加了62ns的延迟才解决。

闭环控制的核心算法是改进型PID,直接上硬核代码:

float step_pid_update(PID_HandleTypeDef *hpid, float error) {
    float p_term = hpid->Kp * error;
    hpid->integral += error * hpid->Ki;
    
    // 抗积分饱和处理
    if(hpid->integral > hpid->max_output) hpid->integral = hpid->max_output;
    else if(hpid->integral < -hpid->max_output) hpid->integral = -hpid->max_output;
    
    float d_term = hpid->Kd * (error - hpid->last_error);
    hpid->last_error = error;
    
    return p_term + hpid->integral + d_term;
}

这里把积分项单独做了限幅处理,实测比传统PID的抗饱和方式更有效。调参时建议先用开环模式让电机转起来,然后慢慢加Kp值直到出现震荡,再回调20%作为初始值。

STM32F401平台闭环步进驱动方案,支持开环模式兼容42,57,60 86两相开环闭环步进电机,提供原理图+PCB+源代码

原理图里有个骚操作:在MOS管驱动部分用了双光耦隔离。虽然成本高了点,但实测在86电机堵转时,普通单光耦方案会出现驱动信号畸变,这个设计直接解决问题。PCB布局时注意把功率地和信号地用0欧电阻单点连接,大电流路径尽量短粗。

现在说下怎么切换开环/闭环模式。代码里用了个状态机:

typedef enum {
    MOTOR_OPENLOOP,
    MOTOR_CLOSEDLOOP,
    MOTOR_HOMING
} Motor_ModeTypeDef;

void motor_mode_switch(Motor_ModeTypeDef new_mode) {
    if(current_mode == new_mode) return;
    
    // 切换时先软停止
    __disable_irq();
    TIM1->CCR1 = 0;
    TIM1->CCR2 = 0;
    
    if(new_mode == MOTOR_CLOSEDLOOP) {
        current_sensor_calibrate();  // 闭环模式需要重新校准电流零点
    }
    
    current_mode = new_mode;
    __enable_irq();
}

注意模式切换时要先停PWM输出,特别是开环切闭环的时候,否则可能因为积分项累积导致电机突然猛转。实测在2000rpm切换时依然稳定,但建议在低速时操作更安全。

这个方案最爽的是兼容性——通过修改microstep_table数组就能适配不同电机:

const uint16_t microstep_table[] = {
    // 全步进模式
    [MICROSTEP_FULL] = {0xFFFF, 0x0000, 0xFFFF, 0x0000},
    // 1/8微步
    [MICROSTEP_8] = {2500,4800,6800,...},  // 实测波形数据
};

不同电机需要不同的微步细分参数,建议先用示波器抓取理想波形,再通过插值算法生成这个表。调试时可以用开环模式先验证波形是否正确,再切闭环加负载测试。

需要源码和设计文件的朋友可以到GitHub搜"STM32F401ClosedLoopStepper",原理图用KiCad打开,源码里有个隐藏技能:长按按键5秒进入参数自整定模式,适合懒人调参。最后提醒下,闭环模式下电机发热会比开环大,建议加散热片或者限制最大电流值。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐