mirror of
https://github.com/odriverobotics/ODrive.git
synced 2026-08-19 02:43:27 +08:00
The InputPort/OutputPort infrastructure facilitates safer data paths between components: OutputPorts store a value and the age of the value measured in number of control loop iterations. InputPorts can be connected to various sources, for instance an OutputPort. InputPorts expose the values to consumers in the form of std::optional to reflect the fact that an InputPort can be dangling or connected to a stale OutputPort.
185 lines
6.6 KiB
C++
185 lines
6.6 KiB
C++
|
|
#include "foc.hpp"
|
|
#include <board.h>
|
|
|
|
Motor::Error AlphaBetaFrameController::on_measurement(
|
|
std::optional<float> vbus_voltage,
|
|
std::optional<std::array<float, 3>> currents,
|
|
uint32_t input_timestamp) {
|
|
|
|
std::optional<float2D> Ialpha_beta;
|
|
|
|
if (currents.has_value()) {
|
|
// Clarke transform
|
|
Ialpha_beta = {
|
|
(*currents)[0],
|
|
one_by_sqrt3 * ((*currents)[1] - (*currents)[2])
|
|
};
|
|
}
|
|
|
|
return on_measurement(vbus_voltage, Ialpha_beta, input_timestamp);
|
|
}
|
|
|
|
Motor::Error AlphaBetaFrameController::get_output(
|
|
uint32_t output_timestamp, float (&pwm_timings)[3],
|
|
std::optional<float>* ibus) {
|
|
std::optional<float2D> mod_alpha_beta;
|
|
Motor::Error status = get_alpha_beta_output(output_timestamp, &mod_alpha_beta, ibus);
|
|
|
|
if (status != Motor::ERROR_NONE) {
|
|
return status;
|
|
} else if (!mod_alpha_beta.has_value() || std::isnan(mod_alpha_beta->first) || std::isnan(mod_alpha_beta->second)) {
|
|
return Motor::ERROR_MODULATION_IS_NAN;
|
|
} else if (SVM(mod_alpha_beta->first, mod_alpha_beta->second, &pwm_timings[0], &pwm_timings[1], &pwm_timings[2]) != 0) {
|
|
return Motor::ERROR_MODULATION_MAGNITUDE;
|
|
}
|
|
|
|
return Motor::ERROR_NONE;
|
|
}
|
|
|
|
void FieldOrientedController::reset() {
|
|
v_current_control_integral_d_ = 0.0f;
|
|
v_current_control_integral_q_ = 0.0f;
|
|
vbus_voltage_measured_ = std::nullopt;
|
|
Ialpha_beta_measured_ = std::nullopt;
|
|
}
|
|
|
|
Motor::Error FieldOrientedController::on_measurement(
|
|
std::optional<float> vbus_voltage, std::optional<float2D> Ialpha_beta,
|
|
uint32_t input_timestamp) {
|
|
// Store the measurements for later processing.
|
|
i_timestamp_ = input_timestamp;
|
|
vbus_voltage_measured_ = vbus_voltage;
|
|
Ialpha_beta_measured_ = Ialpha_beta;
|
|
|
|
return Motor::ERROR_NONE;
|
|
}
|
|
|
|
ODriveIntf::MotorIntf::Error FieldOrientedController::get_alpha_beta_output(
|
|
uint32_t output_timestamp, std::optional<float2D>* mod_alpha_beta,
|
|
std::optional<float>* ibus) {
|
|
|
|
if (!vbus_voltage_measured_.has_value() || !Ialpha_beta_measured_.has_value()) {
|
|
// FOC didn't receive a current measurement yet.
|
|
return Motor::ERROR_CONTROLLER_INITIALIZING;
|
|
} else if (abs((int32_t)(i_timestamp_ - ctrl_timestamp_)) > MAX_CONTROL_LOOP_UPDATE_TO_CURRENT_UPDATE_DELTA) {
|
|
// Data from control loop and current measurement are too far apart.
|
|
return Motor::ERROR_BAD_TIMING;
|
|
}
|
|
|
|
// TODO: improve efficiency in case PWM updates are requested at a higher
|
|
// rate than current sensor updates. In this case we can reuse mod_d and
|
|
// mod_q from a previous iteration.
|
|
|
|
if (!Vdq_setpoint_.has_value()) {
|
|
return Motor::ERROR_UNKNOWN_VOLTAGE_COMMAND;
|
|
} else if (!phase_.has_value() || !phase_vel_.has_value()) {
|
|
return Motor::ERROR_UNKNOWN_PHASE_ESTIMATE;
|
|
} else if (!vbus_voltage_measured_.has_value()) {
|
|
return Motor::ERROR_UNKNOWN_VBUS_VOLTAGE;
|
|
}
|
|
|
|
auto [Vd, Vq] = *Vdq_setpoint_;
|
|
float phase = *phase_;
|
|
float phase_vel = *phase_vel_;
|
|
float vbus_voltage = *vbus_voltage_measured_;
|
|
|
|
std::optional<float2D> Idq;
|
|
|
|
// Park transform
|
|
if (Ialpha_beta_measured_.has_value()) {
|
|
auto [Ialpha, Ibeta] = *Ialpha_beta_measured_;
|
|
float I_phase = phase + phase_vel * ((float)(int32_t)(i_timestamp_ - ctrl_timestamp_) / (float)TIM_1_8_CLOCK_HZ);
|
|
float c_I = our_arm_cos_f32(I_phase);
|
|
float s_I = our_arm_sin_f32(I_phase);
|
|
Idq = {
|
|
c_I * Ialpha + s_I * Ibeta,
|
|
c_I * Ibeta - s_I * Ialpha
|
|
};
|
|
Id_measured_ += I_measured_report_filter_k_ * (Idq->first - Id_measured_);
|
|
Iq_measured_ += I_measured_report_filter_k_ * (Idq->second - Iq_measured_);
|
|
} else {
|
|
Id_measured_ = 0.0f;
|
|
Iq_measured_ = 0.0f;
|
|
}
|
|
|
|
|
|
float mod_to_V = (2.0f / 3.0f) * vbus_voltage;
|
|
float V_to_mod = 1.0f / mod_to_V;
|
|
float mod_d;
|
|
float mod_q;
|
|
|
|
if (enable_current_control_) {
|
|
// Current control mode
|
|
|
|
if (!pi_gains_.has_value()) {
|
|
return Motor::ERROR_UNKNOWN_GAINS;
|
|
} else if (!Idq.has_value()) {
|
|
return Motor::ERROR_UNKNOWN_CURRENT_MEASUREMENT;
|
|
} else if (!Idq_setpoint_.has_value()) {
|
|
return Motor::ERROR_UNKNOWN_CURRENT_COMMAND;
|
|
}
|
|
|
|
auto [p_gain, i_gain] = *pi_gains_;
|
|
auto [Id, Iq] = *Idq;
|
|
auto [Id_setpoint, Iq_setpoint] = *Idq_setpoint_;
|
|
|
|
float Ierr_d = Id_setpoint - Id;
|
|
float Ierr_q = Iq_setpoint - Iq;
|
|
|
|
// Apply PI control (V{d,q}_setpoint act as feed-forward terms in this mode)
|
|
mod_d = V_to_mod * (Vd + v_current_control_integral_d_ + Ierr_d * p_gain);
|
|
mod_q = V_to_mod * (Vq + v_current_control_integral_q_ + Ierr_q * p_gain);
|
|
|
|
// Vector modulation saturation, lock integrator if saturated
|
|
// TODO make maximum modulation configurable
|
|
float mod_scalefactor = 0.80f * sqrt3_by_2 * 1.0f / sqrtf(mod_d * mod_d + mod_q * mod_q);
|
|
if (mod_scalefactor < 1.0f) {
|
|
mod_d *= mod_scalefactor;
|
|
mod_q *= mod_scalefactor;
|
|
// TODO make decayfactor configurable
|
|
v_current_control_integral_d_ *= 0.99f;
|
|
v_current_control_integral_q_ *= 0.99f;
|
|
} else {
|
|
v_current_control_integral_d_ += Ierr_d * (i_gain * current_meas_period);
|
|
v_current_control_integral_q_ += Ierr_q * (i_gain * current_meas_period);
|
|
}
|
|
|
|
} else {
|
|
// Voltage control mode
|
|
mod_d = V_to_mod * Vd;
|
|
mod_q = V_to_mod * Vq;
|
|
}
|
|
|
|
// Inverse park transform
|
|
float pwm_phase = phase + phase_vel * ((float)(int32_t)(output_timestamp - ctrl_timestamp_) / (float)TIM_1_8_CLOCK_HZ);
|
|
float c_p = our_arm_cos_f32(pwm_phase);
|
|
float s_p = our_arm_sin_f32(pwm_phase);
|
|
float mod_alpha = c_p * mod_d - s_p * mod_q;
|
|
float mod_beta = c_p * mod_q + s_p * mod_d;
|
|
|
|
// Report final applied voltage in stationary frame (for sensorless estimator)
|
|
final_v_alpha_ = mod_to_V * mod_alpha;
|
|
final_v_beta_ = mod_to_V * mod_beta;
|
|
|
|
*mod_alpha_beta = {mod_alpha, mod_beta};
|
|
|
|
if (Idq.has_value()) {
|
|
auto [Id, Iq] = *Idq;
|
|
*ibus = mod_d * Id + mod_q * Iq;
|
|
}
|
|
|
|
return Motor::ERROR_NONE;
|
|
}
|
|
|
|
void FieldOrientedController::update(uint32_t timestamp) {
|
|
CRITICAL_SECTION() {
|
|
ctrl_timestamp_ = timestamp;
|
|
enable_current_control_ = enable_current_control_src_;
|
|
Idq_setpoint_ = Idq_setpoint_src_.get_current();
|
|
Vdq_setpoint_ = Vdq_setpoint_src_.get_current();
|
|
phase_ = phase_src_.get_current();
|
|
phase_vel_ = phase_vel_src_.get_current();
|
|
}
|
|
}
|