参考开源闭环步进电机驱动:XDrive
个人软硬件仓库地址:https://gitee.com/qlexcel/closed-loop-stepper-motor
开环步进电机
丢步概念
步进电动机正常工作时,每接收一个控制脉冲就移动一个步距角,即前进一步。若连续地输入控制脉冲,电动机就相应地连续转动。
步进电动机失步包括丢步和越步。丢步时,转子前进的步数小于脉冲数;越步时,转子前进的步数多于脉冲数。一次丢步和越步的步距数等于运行拍数的整数倍。丢步严重时,将使转子停留在一个位置上或围绕一个位置振动,越步严重时,机床将发生过冲。
丢步原因及策略
转子的加速度慢于步进电动机的旋转磁场
解释:
转子的加速度慢于步进电动机的旋转磁场,即低于换相速度时,步进电动机会产生失步。这是因为输入电动机的电能不足,在步进电动机中产生的同步力矩无法使转子速度跟随定子磁场的旋转速度,从而引起失步。由于步进电动机的动态输出转矩随着连续运行频率的上升而降低,因而,凡是比该频率高的工作频率都将产生丢步。这种失步说明步进电动机的转矩不足,拖动能力不够。
解决方法:
a)、使步进电动机本身产生的电磁转矩增大。为此可在额定电流范围内适当加大驱动电流;在高频范围转矩不足时,可适当提高驱动电路的驱动电压;改用转矩大的步进电动机等。
b)、使步进电动机需要克服的转矩减小。为此可适当降低电动机运行频率,以便提高电动机的输出转矩;设定较长的加速时间,以便转子获得足够的能量。
转子的平均速度高于定子磁场的平均旋转速度
解释:
转子的平均速度高于定子磁场的平均旋转速度,这时定子通电励磁的时间较长,大于转子步进一步所需的时间,则转子在步进过程中获得了过多的能量,使得步进电动机产生的输出转矩增大,从而使电动机越步。当用步进电动机驱动那些使负载上、下动作的机构时,更易产生越步现象,这是因为负载向下运动时,电动机所需的转矩减小。
解决方法:
a)减小步进电动机的驱动电流,以便降低步进电动机的输出转矩。
步进电动机及所带负载存在惯性
解释:
由于步进电动机自身及所带负载存在惯性,使得电动机在工作过程中不能立即起动和停止,而是在起动时出现丢步,在停止时发生越步。
解决方法:
a)通过一个加速和减速过程,即以较低的速度起动,而后逐渐加速到某一速度运行,再逐渐减速直至停止。进行合理、平滑的加减速控制是保证步进驱动系统可靠、高效、精确运行的关键。
步进电动机产生共振
共振也是引起失步的一个原因。步进电动机处于连续运行状态时,如果控制脉冲的频率等于步进电动机的固有频率,将产生共振。在一个控制脉冲周期内,振动得不到充分衰减,下一个脉冲就来到,因而在共振频率附近动态误差最大并会导致步进电动机失步。
解决方法:
a)适当减小步进电动机的驱动电流;采用细分驱动方法;采用阻尼方法,包括机械阻尼法。以上方法都能有效消除电动机振荡,避免失步现象发生。
改变方向时丢脉冲
解释:
表现为往任何一个方向都准,但一改变方向就累计偏差,并且次数越多偏得越多;
解决方案:
a)一般的步进驱动器对方向和脉冲信号都有一定的要求,如:方向信号在第一个脉冲上升沿或下降沿(不同的驱动器要求不一样)到来前数微秒被确定,否则会有一个脉冲所运转的角度与实际需要的转向相反,最后故障现象表现为越走越偏,细分越小越明显,解决办法主要用软件改变发脉冲的逻辑或加延时。
软件缺陷
解释:
控制程序导致失步也不少见,需要检查控制程序是不是有问题。
解决方案:
一时找不到问题原因,也有工程师会让步进电机运行一段时间就重新找原点归位。
闭环控制
闭环驱动的原理
首先我们要知道电机分为转子和定子,本质就是两块磁铁。转子是永磁铁,定子是电磁铁,定子的磁性大小和方向可以控制。当两个磁铁没有夹角时,电机就锁住,定子磁场越大,锁定扭矩越大。
当控制定子磁场跟转子产生夹角,转子就会被带动旋转。
转子的位置可以由编码器测量得到,于是我们可以通过算法调整定子的磁场,让磁场跟转子磁场始终保持有夹角,然后定子的磁场大小由相电流大小控制,于是就可以控制电机转速了。
当两个磁场夹角90度,可以获得最大的扭矩。
步进电机的相电流波形如下,两相电流相位差90度。
开环闭环控制区别
讲一下二相步进电机闭环驱动和开环驱动的区别。假设现在上位机主机发DIR/STEP指令给步进驱动器的指令角度是90.5度。
开环的驱动器收到指令后即根据细分电流表调整步进电机的两相电流,将电流矢量也指向90.5度,然后步进电机永磁转子就会在电磁力作用下运动到90.5度这个位置上了,这其中步进电机AB两相的驱动电流在设置好后就不变了,只要接下来没有新的角度指令那就一直保持原样不改变。
闭环控制器则不然,当闭环控制器收到90.5度的指令角度时,首先利用角度传感器(编码器)测出步进电机现在的实际角度假设是90度,然后跟开环的驱动器一样根据细分电流表调整步进电机的两相电流,但是要将电流矢量指向91.8度(指令角度比实际角度大那就在实际角度上加1.8度,反之减1.8度),与开环控制器不同的是闭环的驱动电流大小不是恒定不变的,而是根据角度误差经过PID算法计算出来的,简单的讲就是指令角度和实际角度的误差大那驱动电流就大相反误差小驱动电流就小,误差为0那驱动电流就为0(这就是闭环步进低发热省电的原因)。
硬件讲解
单片机采用STM32F103CBT6
编码器使用MT6816
用一块OLED12832来显示数据。参考链接
电机驱动芯片使用TB67H450(也可以使用其他类似功能的芯片,有很多,比如TI或国产)
驱动电路这里可以讲一下,上图左边是PWM经过低通滤波后得到模拟电压输出,PWM占空比越高模拟电压值越高。低成本的DAC。
右边是两个驱动芯片分别驱动电机的两根相线。
要知道我们的目的是让相电流波形如下,因此我们需要能控制相电流的方向,可以从A+流向A-,也可以从A-流向A+。同时也需要能控制相电流的大小,让相电流呈正弦波变化。
恰好TB67H450就有这样的功能,给IN1和IN2输入不同电平就可以控制电流方向
给VREF输入不同模拟电压值就可以控制OUT1和OUT2之间的电流大小。
代码讲解
主函数
主函数不用多说,就是初始化外设。初始化完成把中断频率改成20KHz,每20KHz频率计算一次相电流方向和大小来控制TB67H450。
void loop(void)
{
Button_Init(); //按键初始化
SSD1306_Init(); //OLED初始化
XDrive_REINui_Init(); //UI初始化
HAL_Delay(1000); //进行关键外设初始化前等待电源稳定
// //存储数据恢复
// Slave_Reg_Init(); //校验Flash数据并配置参数
REIN_DMA_Init();
REIN_ADC_Init(); //初始化ADC DMA方式采集母线电压
REIN_MT6816_Init(); //MT6816传感器初始化
REIN_HW_Elec_Init(); //初始化A4950的控制引脚,4个IO+2个PWM
Signal_MoreIO_Init(); //MoreIO接口初始化
//控制初始化(Control)
Calibration_Init(); //校准器初始化
Motor_Control_Init(); //电机控制初始化
//通讯接口初始化
REIN_GPIO_Modbus_Init(); //RS485_DIR
REIN_UART_Modbus_Init(); //串口初始化
Signal_Modbus_Init(); //Modbu接口初始化
//调整中断配置
LoopIT_Priority_Overlay(); //重写-中断优先级覆盖
LoopIT_SysTick_20KHz(); //重写-系统计时器修改为20KHz
//启动时使用按键触发校准
if(HAL_GPIO_ReadPin(BUTTON_DOWN_GPIO_Port, BUTTON_DOWN_Pin) == GPIO_PIN_RESET)
{
encode_cali.trigger = true; //触发校准
XDrive_REINui_ToCalibration(); //进入UI校准界面
}
//主循环
for(;;)
{
// major_cycle_count++;
time_second_run();
Calibration_Loop_Callback(); //校准器主程序回调 用于校准环节最后的数据解算
}
}
运动控制
下面就是20KHz调用一次的运动控制算法,也是所有程序的核心了。分成5个部分:读编码器、位置补偿、运动控制、硬目标提取、软目标提取。
读编码器:获取当前编码器值,计算当前位置。
位置补偿:计算电机转速。因为从读编码器位置到计算再到硬件输出存在延迟,因此估计下当硬件真正生效时转子的位置差,补偿下。
运动控制:输入当前速度和目标速度(或当前位置和目标位置),使用PID或DCE算法,计算相电流的方向和大小,最终控制TB67H450。
硬目标提取:就是从硬件上获取目标速度(或目标位置),比如脉冲输入、通信设定等。如果是Motor_Mode_PULSE_Location模式,就是就是读取外部脉冲输入个数来获取目标位置。
软目标提取:获取到硬目标后,需要做加减速曲线来使当前速度到达目标速度,如果目标位置太大是不能直接做PID的。经过加减速过程计算后的输出就是软目标。运动控制也是使用软目标和当前值做PID。
void Motor_Control_Callback(void) //电机控制,20K频率执行
{
/************************************ 数据采集 ************************************/
/************************************ 数据采集 ************************************/
int32_t sub_data; //用于各个算差
motor_control.real_lap_location_last = motor_control.real_lap_location; //上次编码器值
motor_control.real_lap_location = mt6816.rectify_angle; //本次编码器值
//跨圈检测
sub_data = motor_control.real_lap_location - motor_control.real_lap_location_last; //电机旋转角度
if(sub_data > (Move_Pulse_NUM >> 1)) sub_data -= Move_Pulse_NUM; //跨圈旋转角度纠正
else if(sub_data < -(Move_Pulse_NUM >> 1)) sub_data += Move_Pulse_NUM;
//读取位置
motor_control.real_location_last = motor_control.real_location; //上次电机位置
motor_control.real_location += sub_data; //电机位置
/************************************ 数据估计 ************************************/
/************************************ 数据估计 ************************************/
//估计速度
motor_control.est_speed_mut += ( ((motor_control.real_location - motor_control.real_location_last) * (CONTROL_FREQ_HZ)) //计算1秒的速度
+ ((int32_t)(motor_control.est_speed << 5) - (int32_t)(motor_control.est_speed)) //est_speed_mut=本次变化值+31倍est_speed
);
motor_control.est_speed = (motor_control.est_speed_mut >> 5); //(取整)(向0取整)(保留符号位) est_speed_mut=32倍est_speed
motor_control.est_speed_mut = ((motor_control.est_speed_mut) - ((motor_control.est_speed << 5))); //(取余,留给下次计算使用)(向0取整)(保留符号位)
//估计位置
motor_control.est_lead_location = Motor_Control_AdvanceCompen(motor_control.est_speed); //根据估计速度计算位置补偿值 补偿计算时间的延迟,延迟时间固定,但是速度越快,位置差越大
motor_control.est_location = motor_control.real_location + motor_control.est_lead_location; //估计位置=实际位置+补偿值
//估计误差
motor_control.est_error = motor_control.soft_location - motor_control.est_location;
/************************************ 运动控制 ************************************/
/************************************ 运动控制 ************************************/
//运行模式分支
switch(motor_control.mode_run)
{
//测试
case Motor_Mode_Debug_Location: Control_DCE_To_Electric(motor_control.soft_location, motor_control.soft_speed); break;
case Motor_Mode_Debug_Speed: Control_PID_To_Electric(motor_control.soft_speed); break;
//停止
case Control_Mode_Stop: REIN_HW_Elec_SetSleep(); break;
//DIG(CAN/RS485)
case Motor_Mode_Digital_Location: Control_DCE_To_Electric(motor_control.soft_location, motor_control.soft_speed); break;
case Motor_Mode_Digital_Speed: Control_PID_To_Electric(motor_control.soft_speed); break;
case Motor_Mode_Digital_Current: Control_Cur_To_Electric(motor_control.soft_current); break;
case Motor_Mode_Digital_Track: Control_DCE_To_Electric(motor_control.soft_location, motor_control.soft_speed); break;
//MoreIO(PWM/PUL)
case Motor_Mode_PWM_Location: Control_DCE_To_Electric(motor_control.soft_location, motor_control.soft_speed); break; //传入目标位置、速度,做PID
case Motor_Mode_PWM_Speed: Control_PID_To_Electric(motor_control.soft_speed); break; //传入目标速度,做PID,计算结果控制A4950
case Motor_Mode_PWM_Current: Control_Cur_To_Electric(motor_control.soft_current); break;
case Motor_Mode_PULSE_Location: Control_DCE_To_Electric(motor_control.soft_location, motor_control.soft_speed); break;
//其他非法模式
default: break;
}
/************************************ 硬目标提取 ************************************/
/************************************ 从硬件上获取的目标,比如脉冲输入、通信设定等 ************************************/
switch(motor_control.mode_run){
//MoreIO(PWM/PUL)
case Motor_Mode_PWM_Location:
case Motor_Mode_PWM_Speed:
case Motor_Mode_PWM_Current:
Signal_MoreIO_Capture_Goal(); //MoreIO接口获取数据
motor_control.goal_location = signal_moreio.goal_location; //获取目标位置
motor_control.goal_speed = signal_moreio.goal_speed; //获取目标速度
motor_control.goal_current