无刷电机带上驱动器,掉电外力拖动有阻力
这个问题一直以为很好理解或者应该是总所周知的,但是竟然很多人好奇问专门做电机控制的工程师,但是竟然很多人说不明白,今天我就好好聊一聊。
原因
-
反电动势效应
当无刷电机在没有电源供电的情况下被外力拖动时,电机内部的转子仍然会转动。这时,电机的绕组切割磁力线,产生反电动势(Back EMF)。这个反电动势会在电机的绕组中产生电流,进而产生阻力。 -
短接制动
当三相绕组短接后,反电动势在绕组中产生电流。由于三相绕组是短接的,这些电流在绕组中形成闭合回路,短接电流在绕组中产生的磁场与转子磁场相互作用,产生电磁制动力。这种制动力与电机的转速成正比,即电机转速越高,产生的制动力越大。 -
电流路径的形成
· 定子绕组中的电流:反电动势在定子绕组中产生电压,这个电压驱动电流在定子绕组中流动。
· 驱动器电路的连接:无刷电机的三相绕组通过连接线与驱动器的三相输出端(U、V、W)相连。即使驱动器掉电,电机绕组中的反电动势仍然会通过这些连接线传输到驱动器。
· 在驱动器的功率开关元件(如MOSFET)上,通常存在体二极管。这些二极管在反向电压超过其导通电压时会导通,允许电流通过。
电流回路示例
- 反电动势在B相产生正电压,A相产生负电压,从而形成由B相流入A相的电流。
- 电流通过A相的体二极管,进入驱动器的电源轨,又B相的体二极管流入地。
- 进入电源轨之后,我们将驱动器上的元器件近似比作一个电阻型负载。此时电流已形成,由于有负载电阻的原因电流并不大,这里可以思考下为什么会有这么大的阻力。
解决办法
-
原因:
工作中大多驱动器的设计都会考虑到这些问题,会来看这个问题的同学多半是自己做的驱动器没有注意这个细节,专门跑到CSDN找原因的。请同学们们仔细看看自己的代码的PWM配置有没有将空闲引脚配置为低,如果为低仍有阻力,继续往后看,你的电流采样的偏置电压基准是否是在初始化时采集。回到上面的问题,电流明明很小为什么会产生明显的阻力?就是因为反电动势驱动驱动器上的元器件工作,上电就行初始化中会有将MOSFET导通的操作,形成短接制动的效果。
-
解决:
(1)将初始化的PWM引脚改为高组态
//STM32
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
//HPM5300
void disable_all_pwm_output(void)
{
/*force pwm*/
pwm_config_force_cmd_timing(DRV_BLDCPWM, pwm_force_immediately);
pwm_enable_pwm_sw_force_output(DRV_BLDCPWM, DRV_BLDC_UH_PWM_OUTPIN);
pwm_enable_pwm_sw_force_output(DRV_BLDCPWM, DRV_BLDC_UL_PWM_OUTPIN);
pwm_enable_pwm_sw_force_output(DRV_BLDCPWM, DRV_BLDC_VH_PWM_OUTPIN);
pwm_enable_pwm_sw_force_output(DRV_BLDCPWM, DRV_BLDC_VL_PWM_OUTPIN);
pwm_enable_pwm_sw_force_output(DRV_BLDCPWM, DRV_BLDC_WH_PWM_OUTPIN);
pwm_enable_pwm_sw_force_output(DRV_BLDCPWM, DRV_BLDC_WL_PWM_OUTPIN);
pwm_set_force_output(DRV_BLDCPWM,
PWM_FORCE_OUTPUT(DRV_BLDC_UH_PWM_OUTPIN, pwm_output_high_z)
| PWM_FORCE_OUTPUT(DRV_BLDC_UL_PWM_OUTPIN, pwm_output_high_z)
| PWM_FORCE_OUTPUT(DRV_BLDC_VH_PWM_OUTPIN, pwm_output_high_z)
| PWM_FORCE_OUTPUT(DRV_BLDC_VL_PWM_OUTPIN, pwm_output_high_z)
| PWM_FORCE_OUTPUT(DRV_BLDC_WH_PWM_OUTPIN, pwm_output_high_z)
| PWM_FORCE_OUTPUT(DRV_BLDC_WL_PWM_OUTPIN, pwm_output_high_z));
pwm_enable_sw_force(DRV_BLDCPWM);
}
int main(void){
disable_all_pwm_output();
my_pwm_init(MY_PWM_FREQUENCY, 500);
while(1){
}
}
根据自己具体的MCU型号做对应处理
(2) 如果是将偏置电压采样放在了mian函数的初始中,较为简单的方法是在采样函数前加一个延时,因为拖动大多并不持续。另外的方法就是在校准中进行偏置电压采样存入flash,常用的方法是在下桥臂关断是再进行一次采样,这时的采样即为偏置电压采样,用作电流采样时的offset进行电流计算,这样的好处就是防止环境或工况变化导致的零漂。
void Motor::current_meas_cb(uint32_t timestamp, std::optional<Iph_ABC_t> current) {
// TODO: this is platform specific
//const float current_meas_period = static_cast<float>(2 * TIM_1_8_PERIOD_CLOCKS * (TIM_1_8_RCR + 1)) / TIM_1_8_CLOCK_HZ;
TaskTimerContext tmr{axis_->task_times_.current_sense};
n_evt_current_measurement_++;
bool dc_calib_valid = (dc_calib_running_since_ >= config_.dc_calib_tau * 7.5f)
&& (abs(DC_calib_.phA) < max_dc_calib_)
&& (abs(DC_calib_.phB) < max_dc_calib_)
&& (abs(DC_calib_.phC) < max_dc_calib_);
if (armed_state_ == 1 || armed_state_ == 2) {
current_meas_ = {0.0f, 0.0f, 0.0f};
armed_state_ += 1;
} else if (current.has_value() && dc_calib_valid) {
current_meas_ = {
current->phA - DC_calib_.phA,
current->phB - DC_calib_.phB,
current->phC - DC_calib_.phC
};
} else {
current_meas_ = std::nullopt;
}
// Run system-level checks (e.g. overvoltage/undervoltage condition)
// The motor might be disarmed in this function. In this case the
// handler will continue to run until the end but it won't have an
// effect on the PWM.
odrv.do_fast_checks();
if (current_meas_.has_value()) {
// Check for violation of current limit
// If Ia + Ib + Ic == 0 holds then we have:
// Inorm^2 = Id^2 + Iq^2 = Ialpha^2 + Ibeta^2 = 2/3 * (Ia^2 + Ib^2 + Ic^2)
float Itrip = effective_current_lim_ + config_.current_lim_margin;
float Inorm_sq = 2.0f / 3.0f * (SQ(current_meas_->phA)
+ SQ(current_meas_->phB)
+ SQ(current_meas_->phC));
// Hack: we disable the current check during motor calibration because
// it tends to briefly overshoot when the motor moves to align flux with I_alpha
if (Inorm_sq > SQ(Itrip)) {
disarm_with_error(ERROR_CURRENT_LIMIT_VIOLATION);
}
} else if (is_armed_) {
// Since we can't check current limits, be safe for now and disarm.
// Theoretically we could continue to operate if there is no active
// current limit.
disarm_with_error(ERROR_UNKNOWN_CURRENT_MEASUREMENT);
}
if (control_law_) {
Error err = control_law_->on_measurement(vbus_voltage,
current_meas_.has_value() ?
std::make_optional(std::array<float, 3>{current_meas_->phA, current_meas_->phB, current_meas_->phC})
: std::nullopt,
timestamp);
if (err != ERROR_NONE) {
disarm_with_error(err);
}
}
}
void Motor::dc_calib_cb(uint32_t timestamp, std::optional<Iph_
ABC_t> current) {
const float dc_calib_period = static_cast<float>(2 * TIM_1_8_PERIOD_CLOCKS * (TIM_1_8_RCR + 1)) / TIM_1_8_CLOCK_HZ;
TaskTimerContext tmr{axis_->task_times_.dc_calib};
if (current.has_value()) {
const float calib_filter_k = std::min(dc_calib_period / config_.dc_calib_tau, 1.0f);
DC_calib_.phA += (current->phA - DC_calib_.phA) * calib_filter_k;
DC_calib_.phB += (current->phB - DC_calib_.phB) * calib_filter_k;
DC_calib_.phC += (current->phC - DC_calib_.phC) * calib_filter_k;
dc_calib_running_since_ += dc_calib_period;
} else {
DC_calib_.phA = 0.0f;
DC_calib_.phB = 0.0f;
DC_calib_.phC = 0.0f;
dc_calib_running_since_ = 0.0f;
}
}