#ifndef __AXIS_HPP #define __AXIS_HPP #ifndef __ODRIVE_MAIN_H #error "This file should not be included directly. Include odrive_main.h instead." #endif enum HomingState_t { HOMING_STATE_IDLE, HOMING_STATE_HOMING, HOMING_STATE_MOVE_TO_ZERO }; class Axis { public: enum Error_t { ERROR_NONE = 0x00, ERROR_INVALID_STATE = 0x01, // void run_control_loop(const T& update_handler) { while (requested_state_ == AXIS_STATE_UNDEFINED) { // look for errors at axis level and also all subcomponents bool checks_ok = do_checks(); // Update all estimators // Note: updates run even if checks fail bool updates_ok = do_updates(); // 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 bool main_continue = update_handler(); // Check we meet deadlines after queueing ++loop_counter_; // 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; } if (!main_continue) break; } } bool run_lockin_spin(); bool run_sensorless_control_loop(); bool run_closed_loop_control_loop(); bool run_idle_loop(); constexpr uint32_t get_watchdog_reset() { return static_cast(std::clamp(config_.watchdog_timeout, 0, UINT32_MAX / (current_meas_hz + 1)) * current_meas_hz); } void run_state_machine_loop(); const AxisHardwareConfig_t& hw_config_; Config_t& config_; Encoder& encoder_; SensorlessEstimator& sensorless_estimator_; Controller& controller_; Motor& motor_; TrapezoidalTrajectory& trap_; Endstop& min_endstop_; Endstop& max_endstop_; osThreadId thread_id_; volatile bool thread_id_valid_ = false; // variables exposed on protocol Error_t 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 GPIO_TypeDef* step_port_; uint16_t step_pin_; GPIO_TypeDef* dir_port_; uint16_t dir_pin_; State_t requested_state_ = AXIS_STATE_STARTUP_SEQUENCE; State_t task_chain_[10] = { AXIS_STATE_UNDEFINED }; State_t& current_state_ = task_chain_[0]; uint32_t loop_counter_ = 0; LockinState_t lockin_state_ = LOCKIN_STATE_INACTIVE; HomingState_t homing_state_ = HOMING_STATE_IDLE; uint32_t last_heartbeat_ = 0; // watchdog uint32_t watchdog_current_value_= 0; // Communication protocol definitions auto make_protocol_definitions() { return make_protocol_member_list( make_protocol_property("error", &error_), make_protocol_ro_property("step_dir_active", &step_dir_active_), make_protocol_ro_property("current_state", ¤t_state_), make_protocol_property("requested_state", &requested_state_), make_protocol_ro_property("loop_counter", &loop_counter_), make_protocol_ro_property("lockin_state", &lockin_state_), make_protocol_ro_property("homing_state", &homing_state_), make_protocol_object("config", make_protocol_property("startup_motor_calibration", &config_.startup_motor_calibration), make_protocol_property("startup_encoder_index_search", &config_.startup_encoder_index_search), make_protocol_property("startup_encoder_offset_calibration", &config_.startup_encoder_offset_calibration), make_protocol_property("startup_closed_loop_control", &config_.startup_closed_loop_control), make_protocol_property("startup_sensorless_control", &config_.startup_sensorless_control), make_protocol_property("startup_homing", &config_.startup_homing), make_protocol_property("enable_step_dir", &config_.enable_step_dir), make_protocol_property("counts_per_step", &config_.counts_per_step), make_protocol_property("watchdog_timeout", &config_.watchdog_timeout), make_protocol_property("step_gpio_pin", &config_.step_gpio_pin, [](void* ctx) { static_cast(ctx)->decode_step_dir_pins(); }, this), make_protocol_property("dir_gpio_pin", &config_.dir_gpio_pin, [](void* ctx) { static_cast(ctx)->decode_step_dir_pins(); }, this), make_protocol_object("lockin", make_protocol_property("current", &config_.lockin.current), make_protocol_property("ramp_time", &config_.lockin.ramp_time), make_protocol_property("ramp_distance", &config_.lockin.ramp_distance), make_protocol_property("accel", &config_.lockin.accel), make_protocol_property("vel", &config_.lockin.vel), make_protocol_property("finish_distance", &config_.lockin.finish_distance), make_protocol_property("finish_on_vel", &config_.lockin.finish_on_vel), make_protocol_property("finish_on_distance", &config_.lockin.finish_on_distance), make_protocol_property("finish_on_enc_idx", &config_.lockin.finish_on_enc_idx) ), make_protocol_property("can_node_id", &config_.can_node_id), make_protocol_property("can_heartbeat_rate_ms", &config_.can_heartbeat_rate_ms) ), make_protocol_object("motor", motor_.make_protocol_definitions()), make_protocol_object("controller", controller_.make_protocol_definitions()), make_protocol_object("encoder", encoder_.make_protocol_definitions()), make_protocol_object("sensorless_estimator", sensorless_estimator_.make_protocol_definitions()), make_protocol_object("trap_traj", trap_.make_protocol_definitions()), make_protocol_object("min_endstop", min_endstop_.make_protocol_definitions()), make_protocol_object("max_endstop", max_endstop_.make_protocol_definitions()), make_protocol_function("watchdog_feed", *this, &Axis::watchdog_feed) ); } }; DEFINE_ENUM_FLAG_OPERATORS(Axis::Error_t) #endif /* __AXIS_HPP */