Files
ODrive/Firmware/MotorControl/axis.hpp
T

302 lines
10 KiB
C++

#ifndef __AXIS_HPP
#define __AXIS_HPP
class Axis;
#include "encoder.hpp"
#include "sensorless_estimator.hpp"
#include "controller.hpp"
#include "trapTraj.hpp"
#include "endstop.hpp"
#include "mechanical_brake.hpp"
#include "low_level.h"
#include "utils.hpp"
#include "communication/interface_uart.h" // TODO: remove once uart_poll() is gone
#include "taskTimer.hpp"
#include <array>
class Axis : public ODriveIntf::AxisIntf {
public:
struct LockinConfig_t {
float current = 10.0f; // [A]
float ramp_time = 0.4f; // [s]
float ramp_distance = 1 * M_PI; // [rad]
float accel = 20.0f; // [rad/s^2]
float vel = 40.0f; // [rad/s]
float finish_distance = 100.0f; // [rad]
bool finish_on_vel = false;
bool finish_on_distance = false;
bool finish_on_enc_idx = false;
};
struct TaskTimes_t {
TaskTimer thermistor_update;
TaskTimer encoder_update;
TaskTimer sensorless_update;
TaskTimer min_endstop_update;
TaskTimer max_endstop_update;
TaskTimer axis_update;
TaskTimer axis_error_check;
TaskTimer controller_update;
TaskTimer motor_update;
TaskTimer update_handler;
TaskTimer brake_update;
TaskTimer adc_cb;
TaskTimer control_loop;
TaskTimer total;
TaskTimer uart_poll;
TaskTimer FOC_Current;
};
static LockinConfig_t default_calibration();
static LockinConfig_t default_sensorless();
static LockinConfig_t default_lockin();
struct CANConfig_t {
uint32_t node_id = 0;
bool is_extended = false;
uint32_t heartbeat_rate_ms = 100;
uint32_t encoder_rate_ms = 10;
};
struct Config_t {
bool startup_motor_calibration = false; //<! run motor calibration at startup, skip otherwise
bool startup_encoder_index_search = false; //<! run encoder index search after startup, skip otherwise
// this only has an effect if encoder.config.use_index is also true
bool startup_encoder_offset_calibration = false; //<! run encoder offset calibration after startup, skip otherwise
bool startup_closed_loop_control = false; //<! enable closed loop control after calibration/startup
bool startup_sensorless_control = false; //<! enable sensorless control after calibration/startup
bool startup_homing = false; //<! enable homing after calibration/startup
bool enable_step_dir = false; //<! enable step/dir input after calibration
// For M0 this has no effect if enable_uart is true
bool step_dir_always_on = false; //<! Keep step/dir enabled while the motor is disabled.
//<! This is ignored if enable_step_dir is false.
//<! This setting only takes effect on a state transition
//<! into idle or out of closed loop control.
float turns_per_step = 1.0f / 1024.0f;
float watchdog_timeout = 0.0f; // [s]
bool enable_watchdog = false;
// Defaults loaded from hw_config in load_configuration in main.cpp
uint16_t step_gpio_pin = 0;
uint16_t dir_gpio_pin = 0;
LockinConfig_t calibration_lockin = default_calibration();
LockinConfig_t sensorless_ramp = default_sensorless();
LockinConfig_t general_lockin;
CANConfig_t can;
// custom setters
Axis* parent = nullptr;
void set_step_gpio_pin(uint16_t value) { step_gpio_pin = value; parent->decode_step_dir_pins(); }
void set_dir_gpio_pin(uint16_t value) { dir_gpio_pin = value; parent->decode_step_dir_pins(); }
};
struct Homing_t {
bool is_homed = false;
};
struct CAN_t {
uint32_t last_heartbeat = 0;
uint32_t last_encoder = 0;
};
enum thread_signals {
M_SIGNAL_PH_CURRENT_MEAS = 1u << 0
};
Axis(int axis_num,
uint16_t default_step_gpio_pin,
uint16_t default_dir_gpio_pin,
osPriority thread_priority,
Encoder& encoder,
SensorlessEstimator& sensorless_estimator,
Controller& controller,
Motor& motor,
TrapezoidalTrajectory& trap,
Endstop& min_endstop,
Endstop& max_endstop,
MechanicalBrake& mechanical_brake);
bool apply_config();
void clear_config();
bool setup();
void start_thread();
void signal_current_meas();
bool wait_for_current_meas();
void step_cb();
void set_step_dir_active(bool enable);
void decode_step_dir_pins();
bool check_DRV_fault();
bool check_PSU_brownout();
bool do_checks();
bool do_updates();
void watchdog_feed();
bool watchdog_check();
void clear_errors() {
motor_.error_ = Motor::ERROR_NONE;
controller_.error_ = Controller::ERROR_NONE;
sensorless_estimator_.error_ = SensorlessEstimator::ERROR_NONE;
encoder_.error_ = Encoder::ERROR_NONE;
encoder_.spi_error_rate_ = 0.0f;
error_ = ERROR_NONE;
}
// True if there are no errors
bool inline check_for_errors() {
return error_ == ERROR_NONE;
}
// @brief Runs the specified update handler at the frequency of the current measurements.
//
// The loop runs until one of the following conditions:
// - update_handler returns false
// - the current measurement times out
// - the health checks fail (brownout, driver fault line)
// - update_handler doesn't update the modulation timings in time
// This criterion is ignored if current_state is AXIS_STATE_IDLE
//
// If update_handler is going to update the motor timings, you must call motor.arm()
// shortly before this function.
//
// If the function returns, it is guaranteed that error is non-zero, except if the cause
// for the exit was a negative return value of update_handler or an external
// state change request (requested_state != AXIS_STATE_DONT_CARE).
// Under all exit conditions the motor is disarmed and the brake current set to zero.
// Furthermore, if the update_handler does not set the phase voltages in time, they will
// go to zero.
//
// @tparam T Must be a callable type that takes no arguments and returns a bool
template<typename T>
void run_control_loop(const T& update_handler) {
while (requested_state_ == AXIS_STATE_UNDEFINED) {
task_times_.control_loop.beginTimer();
// look for errors at axis level and also all subcomponents
task_times_.axis_error_check.beginTimer();
bool checks_ok = do_checks();
task_times_.axis_error_check.stopTimer();
// Update all estimators
// Note: updates run even if checks fail
task_times_.axis_update.beginTimer();
bool updates_ok = do_updates();
task_times_.axis_update.stopTimer();
// make sure the watchdog is being fed.
bool watchdog_ok = watchdog_check();
if (!checks_ok || !updates_ok || !watchdog_ok) {
// It's not useful to quit idle since that is the safe action
// Also leaving idle would rearm the motors
if (current_state_ != AXIS_STATE_IDLE)
break;
}
// Run main loop function, defer quitting for after wait
// TODO: change arming logic to arm after waiting
task_times_.update_handler.beginTimer();
bool main_continue = update_handler();
task_times_.update_handler.stopTimer();
if (axis_num_ == 0) {
task_times_.uart_poll.beginTimer();
uart_poll(); // TODO: move to board-level control loop once it exists
task_times_.uart_poll.stopTimer();
}
// Check we meet deadlines after queueing
++loop_counter_;
task_times_.control_loop.stopTimer();
task_times_.total.stopTimer();
if(axis_num_ == 1)
TaskTimer::sample_next = false;
// Wait until the current measurement interrupt fires
if (!wait_for_current_meas()) {
// maybe the interrupt handler is dead, let's be
// safe and float the phases
safety_critical_disarm_motor_pwm(motor_);
update_brake_current();
error_ |= ERROR_CURRENT_MEASUREMENT_TIMEOUT;
break;
}
task_times_.total.beginTimer();
if (!main_continue)
break;
}
}
bool run_lockin_spin(const LockinConfig_t &lockin_config);
bool run_sensorless_control_loop();
bool run_closed_loop_control_loop();
bool run_homing();
bool run_idle_loop();
constexpr uint32_t get_watchdog_reset() {
return static_cast<uint32_t>(std::clamp<float>(config_.watchdog_timeout, 0, UINT32_MAX / (current_meas_hz + 1)) * current_meas_hz);
}
void run_state_machine_loop();
// hardware config
int axis_num_;
uint16_t default_step_gpio_pin_;
uint16_t default_dir_gpio_pin_;
osPriority thread_priority_;
Config_t config_;
Encoder& encoder_;
SensorlessEstimator& sensorless_estimator_;
Controller& controller_;
Motor& motor_;
TrapezoidalTrajectory& trap_traj_;
Endstop& min_endstop_;
Endstop& max_endstop_;
MechanicalBrake& mechanical_brake_;
TaskTimes_t task_times_;
osThreadId thread_id_;
const uint32_t stack_size_ = 2048; // Bytes
volatile bool thread_id_valid_ = false;
// variables exposed on protocol
Error error_ = ERROR_NONE;
bool step_dir_active_ = false; // auto enabled after calibration, based on config.enable_step_dir
// updated from config in constructor, and on protocol hook
Stm32Gpio step_gpio_;
Stm32Gpio dir_gpio_;
AxisState requested_state_ = AXIS_STATE_STARTUP_SEQUENCE;
std::array<AxisState, 10> task_chain_ = { AXIS_STATE_UNDEFINED };
AxisState& current_state_ = task_chain_.front();
uint32_t loop_counter_ = 0;
LockinState lockin_state_ = LOCKIN_STATE_INACTIVE;
Homing_t homing_;
CAN_t can_;
// watchdog
uint32_t watchdog_current_value_= 0;
};
#endif /* __AXIS_HPP */