mirror of
https://github.com/odriverobotics/ODrive.git
synced 2026-09-21 07:14:22 +08:00
Initial implementation of interface autogenerator.
TODO: - write to endpoints from float (for PWM/analog input) - ascii protocol
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
|
||||
#build folder
|
||||
autogen/
|
||||
build/
|
||||
deploy/
|
||||
.dep/
|
||||
|
||||
@@ -161,9 +161,9 @@ bool Axis::do_checks() {
|
||||
if ((current_state_ != AXIS_STATE_IDLE) && (motor_.armed_state_ == Motor::ARMED_STATE_DISARMED))
|
||||
// motor got disarmed in something other than the idle loop
|
||||
error_ |= ERROR_MOTOR_DISARMED;
|
||||
if (!(vbus_voltage >= board_config.dc_bus_undervoltage_trip_level))
|
||||
if (!(vbus_voltage >= odrv.config_.dc_bus_undervoltage_trip_level))
|
||||
error_ |= ERROR_DC_BUS_UNDER_VOLTAGE;
|
||||
if (!(vbus_voltage <= board_config.dc_bus_overvoltage_trip_level))
|
||||
if (!(vbus_voltage <= odrv.config_.dc_bus_overvoltage_trip_level))
|
||||
error_ |= ERROR_DC_BUS_OVER_VOLTAGE;
|
||||
|
||||
// Sub-components should use set_error which will propegate to this error_
|
||||
|
||||
+11
-116
@@ -5,43 +5,8 @@
|
||||
#error "This file should not be included directly. Include odrive_main.h instead."
|
||||
#endif
|
||||
|
||||
class Axis {
|
||||
class Axis : public AxisIntf {
|
||||
public:
|
||||
enum Error {
|
||||
ERROR_NONE = 0x00,
|
||||
ERROR_INVALID_STATE = 0x01, //<! an invalid state was requested
|
||||
ERROR_DC_BUS_UNDER_VOLTAGE = 0x02,
|
||||
ERROR_DC_BUS_OVER_VOLTAGE = 0x04,
|
||||
ERROR_CURRENT_MEASUREMENT_TIMEOUT = 0x08,
|
||||
ERROR_BRAKE_RESISTOR_DISARMED = 0x10, //<! the brake resistor was unexpectedly disarmed
|
||||
ERROR_MOTOR_DISARMED = 0x20, //<! the motor was unexpectedly disarmed
|
||||
ERROR_MOTOR_FAILED = 0x40, // Go to motor.hpp for information, check odrvX.axisX.motor.error for error value
|
||||
ERROR_SENSORLESS_ESTIMATOR_FAILED = 0x80,
|
||||
ERROR_ENCODER_FAILED = 0x100, // Go to encoder.hpp for information, check odrvX.axisX.encoder.error for error value
|
||||
ERROR_CONTROLLER_FAILED = 0x200,
|
||||
ERROR_POS_CTRL_DURING_SENSORLESS = 0x400, // DEPRECATED
|
||||
ERROR_WATCHDOG_TIMER_EXPIRED = 0x800,
|
||||
ERROR_MIN_ENDSTOP_PRESSED = 0x1000,
|
||||
ERROR_MAX_ENDSTOP_PRESSED = 0x2000,
|
||||
ERROR_ESTOP_REQUESTED = 0x4000,
|
||||
ERROR_HOMING_WITHOUT_ENDSTOP = 0x20000, // the min endstop was not enabled during homing
|
||||
};
|
||||
|
||||
enum State_t {
|
||||
AXIS_STATE_UNDEFINED = 0, //<! will fall through to idle
|
||||
AXIS_STATE_IDLE = 1, //<! disable PWM and do nothing
|
||||
AXIS_STATE_STARTUP_SEQUENCE = 2, //<! the actual sequence is defined by the config.startup_... flags
|
||||
AXIS_STATE_FULL_CALIBRATION_SEQUENCE = 3, //<! run all calibration procedures, then idle
|
||||
AXIS_STATE_MOTOR_CALIBRATION = 4, //<! run motor calibration
|
||||
AXIS_STATE_SENSORLESS_CONTROL = 5, //<! run sensorless control
|
||||
AXIS_STATE_ENCODER_INDEX_SEARCH = 6, //<! run encoder index search
|
||||
AXIS_STATE_ENCODER_OFFSET_CALIBRATION = 7, //<! run encoder offset calibration
|
||||
AXIS_STATE_CLOSED_LOOP_CONTROL = 8, //<! run closed loop control
|
||||
AXIS_STATE_LOCKIN_SPIN = 9, //<! run lockin spin
|
||||
AXIS_STATE_ENCODER_DIR_FIND = 10,
|
||||
AXIS_STATE_HOMING = 11, //<! run axis homing function
|
||||
};
|
||||
|
||||
struct LockinConfig_t {
|
||||
float current = 10.0f; // [A]
|
||||
float ramp_time = 0.4f; // [s]
|
||||
@@ -88,6 +53,11 @@ public:
|
||||
LockinConfig_t general_lockin;
|
||||
uint8_t can_node_id = 0; // Both axes will have the same id to start
|
||||
uint32_t can_heartbeat_rate_ms = 100;
|
||||
|
||||
// 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 {
|
||||
@@ -98,13 +68,6 @@ public:
|
||||
M_SIGNAL_PH_CURRENT_MEAS = 1u << 0
|
||||
};
|
||||
|
||||
enum LockinState_t {
|
||||
LOCKIN_STATE_INACTIVE,
|
||||
LOCKIN_STATE_RAMP,
|
||||
LOCKIN_STATE_ACCELERATE,
|
||||
LOCKIN_STATE_CONST_VEL,
|
||||
};
|
||||
|
||||
Axis(int axis_num,
|
||||
const AxisHardwareConfig_t& hw_config,
|
||||
Config_t& config,
|
||||
@@ -143,7 +106,7 @@ public:
|
||||
sensorless_estimator_.error_ = SensorlessEstimator::ERROR_NONE;
|
||||
encoder_.error_ = Encoder::ERROR_NONE;
|
||||
|
||||
error_ = Axis::ERROR_NONE;
|
||||
error_ = ERROR_NONE;
|
||||
}
|
||||
|
||||
// True if there are no errors
|
||||
@@ -250,85 +213,17 @@ public:
|
||||
GPIO_TypeDef* dir_port_;
|
||||
uint16_t dir_pin_;
|
||||
|
||||
State_t requested_state_ = AXIS_STATE_STARTUP_SEQUENCE;
|
||||
std::array<State_t, 10> task_chain_ = { AXIS_STATE_UNDEFINED };
|
||||
State_t& current_state_ = task_chain_.front();
|
||||
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_t lockin_state_ = LOCKIN_STATE_INACTIVE;
|
||||
LockinState lockin_state_ = LOCKIN_STATE_INACTIVE;
|
||||
Homing_t homing_;
|
||||
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_property("is_homed", &homing_.is_homed),
|
||||
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("step_dir_always_on", &config_.step_dir_always_on),
|
||||
make_protocol_property("counts_per_step", &config_.counts_per_step),
|
||||
make_protocol_property("watchdog_timeout", &config_.watchdog_timeout),
|
||||
make_protocol_property("enable_watchdog", &config_.enable_watchdog),
|
||||
make_protocol_property("step_gpio_pin", &config_.step_gpio_pin,
|
||||
[](void* ctx) { static_cast<Axis*>(ctx)->decode_step_dir_pins(); }, this),
|
||||
make_protocol_property("dir_gpio_pin", &config_.dir_gpio_pin,
|
||||
[](void* ctx) { static_cast<Axis*>(ctx)->decode_step_dir_pins(); }, this),
|
||||
make_protocol_object("calibration_lockin",
|
||||
make_protocol_property("current", &config_.calibration_lockin.current),
|
||||
make_protocol_property("ramp_time", &config_.calibration_lockin.ramp_time),
|
||||
make_protocol_property("ramp_distance", &config_.calibration_lockin.ramp_distance),
|
||||
make_protocol_property("accel", &config_.calibration_lockin.accel),
|
||||
make_protocol_property("vel", &config_.calibration_lockin.vel)),
|
||||
make_protocol_object("sensorless_ramp",
|
||||
make_protocol_property("current", &config_.sensorless_ramp.current),
|
||||
make_protocol_property("ramp_time", &config_.sensorless_ramp.ramp_time),
|
||||
make_protocol_property("ramp_distance", &config_.sensorless_ramp.ramp_distance),
|
||||
make_protocol_property("accel", &config_.sensorless_ramp.accel),
|
||||
make_protocol_property("vel", &config_.sensorless_ramp.vel),
|
||||
make_protocol_property("finish_distance", &config_.sensorless_ramp.finish_distance),
|
||||
make_protocol_property("finish_on_vel", &config_.sensorless_ramp.finish_on_vel),
|
||||
make_protocol_property("finish_on_distance", &config_.sensorless_ramp.finish_on_distance),
|
||||
make_protocol_property("finish_on_enc_idx", &config_.sensorless_ramp.finish_on_enc_idx)),
|
||||
make_protocol_object("general_lockin",
|
||||
make_protocol_property("current", &config_.general_lockin.current),
|
||||
make_protocol_property("ramp_time", &config_.general_lockin.ramp_time),
|
||||
make_protocol_property("ramp_distance", &config_.general_lockin.ramp_distance),
|
||||
make_protocol_property("accel", &config_.general_lockin.accel),
|
||||
make_protocol_property("vel", &config_.general_lockin.vel),
|
||||
make_protocol_property("finish_distance", &config_.general_lockin.finish_distance),
|
||||
make_protocol_property("finish_on_vel", &config_.general_lockin.finish_on_vel),
|
||||
make_protocol_property("finish_on_distance", &config_.general_lockin.finish_on_distance),
|
||||
make_protocol_property("finish_on_enc_idx", &config_.general_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_traj_.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),
|
||||
make_protocol_function("clear_errors", *this, &Axis::clear_errors)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
DEFINE_ENUM_FLAG_OPERATORS(Axis::Error)
|
||||
|
||||
#endif /* __AXIS_HPP */
|
||||
|
||||
@@ -5,38 +5,8 @@
|
||||
#error "This file should not be included directly. Include odrive_main.h instead."
|
||||
#endif
|
||||
|
||||
class Controller {
|
||||
class Controller : public ControllerIntf {
|
||||
public:
|
||||
enum Error {
|
||||
ERROR_NONE = 0,
|
||||
ERROR_OVERSPEED = 0x01,
|
||||
ERROR_INVALID_INPUT_MODE = 0x02,
|
||||
ERROR_UNSTABLE_GAIN = 0x04,
|
||||
ERROR_INVALID_MIRROR_AXIS = 0x08,
|
||||
ERROR_INVALID_LOAD_ENCODER = 0x10,
|
||||
ERROR_INVALID_ESTIMATE = 0x20,
|
||||
};
|
||||
|
||||
// Note: these should be sorted from lowest level of control to
|
||||
// highest level of control, to allow "<" style comparisons.
|
||||
enum ControlMode{
|
||||
CONTROL_MODE_VOLTAGE_CONTROL = 0,
|
||||
CONTROL_MODE_CURRENT_CONTROL = 1,
|
||||
CONTROL_MODE_VELOCITY_CONTROL = 2,
|
||||
CONTROL_MODE_POSITION_CONTROL = 3
|
||||
};
|
||||
|
||||
enum InputMode{
|
||||
INPUT_MODE_INACTIVE,
|
||||
INPUT_MODE_PASSTHROUGH,
|
||||
INPUT_MODE_VEL_RAMP,
|
||||
INPUT_MODE_POS_FILTER,
|
||||
INPUT_MODE_MIX_CHANNELS,
|
||||
INPUT_MODE_TRAP_TRAJ,
|
||||
INPUT_MODE_CURRENT_RAMP,
|
||||
INPUT_MODE_MIRROR,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint32_t index = 0;
|
||||
float cogging_map[3600];
|
||||
@@ -48,7 +18,7 @@ public:
|
||||
bool anticogging_enabled = true;
|
||||
} Anticogging_t;
|
||||
|
||||
struct Config_t {
|
||||
struct Config_t : ConfigIntf {
|
||||
ControlMode control_mode = CONTROL_MODE_POSITION_CONTROL; //see: ControlMode
|
||||
InputMode input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode
|
||||
float pos_gain = 20.0f; // [(counts/s) / counts]
|
||||
@@ -72,6 +42,10 @@ public:
|
||||
uint8_t axis_to_mirror = -1;
|
||||
float mirror_ratio = 1.0f;
|
||||
uint8_t load_encoder_axis = -1; // default depends on Axis number and is set in load_configuration()
|
||||
|
||||
// custom setters
|
||||
Controller* parent;
|
||||
void set_input_filter_bandwidth(float value) { input_filter_bandwidth = value; parent->update_filter_gains(); }
|
||||
};
|
||||
|
||||
explicit Controller(Config_t& config);
|
||||
@@ -121,56 +95,8 @@ public:
|
||||
|
||||
bool anticogging_valid_ = false;
|
||||
|
||||
// Communication protocol definitions
|
||||
auto make_protocol_definitions() {
|
||||
return make_protocol_member_list(
|
||||
make_protocol_property("error", &error_),
|
||||
make_protocol_property("input_pos", &input_pos_,
|
||||
[](void* ctx) { static_cast<Controller*>(ctx)->input_pos_updated(); }, this),
|
||||
make_protocol_property("input_vel", &input_vel_),
|
||||
make_protocol_property("input_current", &input_current_),
|
||||
make_protocol_ro_property("pos_setpoint", &pos_setpoint_),
|
||||
make_protocol_ro_property("vel_setpoint", &vel_setpoint_),
|
||||
make_protocol_ro_property("current_setpoint", ¤t_setpoint_),
|
||||
make_protocol_ro_property("trajectory_done", &trajectory_done_),
|
||||
make_protocol_property("vel_integrator_current", &vel_integrator_current_),
|
||||
make_protocol_property("anticogging_valid", &anticogging_valid_),
|
||||
make_protocol_property("gain_scheduling_width", &config_.gain_scheduling_width),
|
||||
make_protocol_object("config",
|
||||
make_protocol_property("enable_vel_limit", &config_.enable_vel_limit),
|
||||
make_protocol_property("enable_current_mode_vel_limit", &config_.enable_current_mode_vel_limit),
|
||||
make_protocol_property("enable_gain_scheduling", &config_.enable_gain_scheduling),
|
||||
make_protocol_property("enable_overspeed_error", &config_.enable_overspeed_error),
|
||||
make_protocol_property("control_mode", &config_.control_mode),
|
||||
make_protocol_property("input_mode", &config_.input_mode),
|
||||
make_protocol_property("pos_gain", &config_.pos_gain),
|
||||
make_protocol_property("vel_gain", &config_.vel_gain),
|
||||
make_protocol_property("vel_integrator_gain", &config_.vel_integrator_gain),
|
||||
make_protocol_property("vel_limit", &config_.vel_limit),
|
||||
make_protocol_property("vel_limit_tolerance", &config_.vel_limit_tolerance),
|
||||
make_protocol_property("vel_ramp_rate", &config_.vel_ramp_rate),
|
||||
make_protocol_property("current_ramp_rate", &config_.current_ramp_rate),
|
||||
make_protocol_property("homing_speed", &config_.homing_speed),
|
||||
make_protocol_property("inertia", &config_.inertia),
|
||||
make_protocol_property("axis_to_mirror", &config_.axis_to_mirror),
|
||||
make_protocol_property("mirror_ratio", &config_.mirror_ratio),
|
||||
make_protocol_property("load_encoder_axis", &config_.load_encoder_axis),
|
||||
make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth,
|
||||
[](void* ctx) { static_cast<Controller*>(ctx)->update_filter_gains(); }, this),
|
||||
make_protocol_object("anticogging",
|
||||
make_protocol_ro_property("index", &config_.anticogging.index),
|
||||
make_protocol_property("pre_calibrated", &config_.anticogging.pre_calibrated),
|
||||
make_protocol_ro_property("calib_anticogging", &config_.anticogging.calib_anticogging),
|
||||
make_protocol_property("calib_pos_threshold", &config_.anticogging.calib_pos_threshold),
|
||||
make_protocol_property("calib_vel_threshold", &config_.anticogging.calib_vel_threshold),
|
||||
make_protocol_ro_property("cogging_ratio", &config_.anticogging.cogging_ratio),
|
||||
make_protocol_property("anticogging_enabled", &config_.anticogging.anticogging_enabled))),
|
||||
make_protocol_function("move_incremental", *this, &Controller::move_incremental, "displacement", "from_goal_point"),
|
||||
make_protocol_function("start_anticogging_calibration", *this, &Controller::start_anticogging_calibration)
|
||||
);
|
||||
}
|
||||
// custom setters
|
||||
void set_input_pos(float value) { input_pos_ = value; input_pos_updated(); }
|
||||
};
|
||||
|
||||
DEFINE_ENUM_FLAG_OPERATORS(Controller::Error)
|
||||
|
||||
#endif // __CONTROLLER_HPP
|
||||
|
||||
@@ -5,33 +5,12 @@
|
||||
#error "This file should not be included directly. Include odrive_main.h instead."
|
||||
#endif
|
||||
|
||||
class Encoder {
|
||||
class Encoder : public EncoderIntf {
|
||||
public:
|
||||
enum Error {
|
||||
ERROR_NONE = 0,
|
||||
ERROR_UNSTABLE_GAIN = 0x01,
|
||||
ERROR_CPR_POLEPAIRS_MISMATCH = 0x02,
|
||||
ERROR_NO_RESPONSE = 0x04,
|
||||
ERROR_UNSUPPORTED_ENCODER_MODE = 0x08,
|
||||
ERROR_ILLEGAL_HALL_STATE = 0x10,
|
||||
ERROR_INDEX_NOT_FOUND_YET = 0x20,
|
||||
ERROR_ABS_SPI_TIMEOUT = 0x40,
|
||||
ERROR_ABS_SPI_COM_FAIL = 0x80,
|
||||
ERROR_ABS_SPI_NOT_READY = 0x100,
|
||||
};
|
||||
|
||||
enum Mode_t {
|
||||
MODE_INCREMENTAL,
|
||||
MODE_HALL,
|
||||
MODE_SINCOS,
|
||||
MODE_SPI_ABS_CUI = 0x100, //!< compatible with CUI AMT23xx
|
||||
MODE_SPI_ABS_AMS = 0x101, //!< compatible with AMS AS5047P, AS5048A/AS5048B (no daisy chain support)
|
||||
MODE_SPI_ABS_AEAT = 0x102, //!< not yet implemented
|
||||
};
|
||||
const uint32_t MODE_FLAG_ABS = 0x100;
|
||||
|
||||
struct Config_t {
|
||||
Encoder::Mode_t mode = Encoder::MODE_INCREMENTAL;
|
||||
struct Config_t : EncoderIntf::ConfigIntf {
|
||||
Mode mode = MODE_INCREMENTAL;
|
||||
bool use_index = false;
|
||||
bool pre_calibrated = false; // If true, this means the offset stored in
|
||||
// configuration is valid and does not need
|
||||
@@ -53,6 +32,14 @@ public:
|
||||
uint16_t abs_spi_cs_gpio_pin = 1;
|
||||
uint16_t sincos_gpio_pin_sin = 3;
|
||||
uint16_t sincos_gpio_pin_cos = 4;
|
||||
|
||||
// custom setters
|
||||
Encoder* parent = nullptr;
|
||||
void set_use_index(bool value) { use_index = value; parent->set_idx_subscribe(); }
|
||||
void set_find_idx_on_lockin_only(bool value) { find_idx_on_lockin_only = value; parent->set_idx_subscribe(); }
|
||||
void set_abs_spi_cs_gpio_pin(uint16_t value) { abs_spi_cs_gpio_pin = value; parent->abs_spi_cs_pin_init(); }
|
||||
void set_pre_calibrated(bool value) { pre_calibrated = value; parent->check_pre_calibrated(); }
|
||||
void set_bandwidth(float value) { bandwidth = value; parent->update_pll_gains(); }
|
||||
};
|
||||
|
||||
Encoder(const EncoderHardwareConfig_t& hw_config,
|
||||
@@ -113,7 +100,7 @@ public:
|
||||
uint16_t abs_spi_dma_tx_[1] = {0xFFFF};
|
||||
uint16_t abs_spi_dma_rx_[1];
|
||||
bool abs_spi_pos_updated_ = false;
|
||||
Mode_t mode_ = MODE_INCREMENTAL;
|
||||
Mode mode_ = MODE_INCREMENTAL;
|
||||
GPIO_TypeDef* abs_spi_cs_port_;
|
||||
uint16_t abs_spi_cs_pin_;
|
||||
uint32_t abs_spi_cr1;
|
||||
@@ -122,55 +109,6 @@ public:
|
||||
constexpr float getCoggingRatio(){
|
||||
return config_.cpr / 3600.0f;
|
||||
}
|
||||
|
||||
// Communication protocol definitions
|
||||
auto make_protocol_definitions() {
|
||||
return make_protocol_member_list(
|
||||
make_protocol_property("error", &error_),
|
||||
make_protocol_ro_property("is_ready", &is_ready_),
|
||||
make_protocol_ro_property("index_found", const_cast<bool*>(&index_found_)),
|
||||
make_protocol_ro_property("shadow_count", &shadow_count_),
|
||||
make_protocol_ro_property("count_in_cpr", &count_in_cpr_),
|
||||
make_protocol_ro_property("interpolation", &interpolation_),
|
||||
make_protocol_ro_property("phase", &phase_),
|
||||
make_protocol_ro_property("pos_estimate", &pos_estimate_),
|
||||
make_protocol_ro_property("pos_cpr", &pos_cpr_),
|
||||
make_protocol_ro_property("hall_state", &hall_state_),
|
||||
make_protocol_ro_property("vel_estimate", &vel_estimate_),
|
||||
make_protocol_ro_property("calib_scan_response", &calib_scan_response_),
|
||||
make_protocol_property("pos_abs", &pos_abs_),
|
||||
make_protocol_ro_property("spi_error_rate", &spi_error_rate_),
|
||||
|
||||
make_protocol_object("config",
|
||||
make_protocol_property("mode", &config_.mode),
|
||||
make_protocol_property("use_index", &config_.use_index,
|
||||
[](void* ctx) { static_cast<Encoder*>(ctx)->set_idx_subscribe(); }, this),
|
||||
make_protocol_property("find_idx_on_lockin_only", &config_.find_idx_on_lockin_only,
|
||||
[](void* ctx) { static_cast<Encoder*>(ctx)->set_idx_subscribe(); }, this),
|
||||
make_protocol_property("abs_spi_cs_gpio_pin", &config_.abs_spi_cs_gpio_pin,
|
||||
[](void* ctx) { static_cast<Encoder*>(ctx)->abs_spi_cs_pin_init(); }, this),
|
||||
make_protocol_property("zero_count_on_find_idx", &config_.zero_count_on_find_idx),
|
||||
make_protocol_property("cpr", &config_.cpr),
|
||||
make_protocol_property("offset", &config_.offset),
|
||||
make_protocol_property("pre_calibrated", &config_.pre_calibrated,
|
||||
[](void* ctx) { static_cast<Encoder*>(ctx)->check_pre_calibrated(); }, this),
|
||||
make_protocol_property("offset_float", &config_.offset_float),
|
||||
make_protocol_property("enable_phase_interpolation", &config_.enable_phase_interpolation),
|
||||
make_protocol_property("bandwidth", &config_.bandwidth,
|
||||
[](void* ctx) { static_cast<Encoder*>(ctx)->update_pll_gains(); }, this),
|
||||
make_protocol_property("calib_range", &config_.calib_range),
|
||||
make_protocol_property("calib_scan_distance", &config_.calib_scan_distance),
|
||||
make_protocol_property("calib_scan_omega", &config_.calib_scan_omega),
|
||||
make_protocol_property("idx_search_unidirectional", &config_.idx_search_unidirectional),
|
||||
make_protocol_property("ignore_illegal_hall_state", &config_.ignore_illegal_hall_state),
|
||||
make_protocol_property("sincos_gpio_pin_sin", &config_.sincos_gpio_pin_sin),
|
||||
make_protocol_property("sincos_gpio_pin_cos", &config_.sincos_gpio_pin_cos)
|
||||
),
|
||||
make_protocol_function("set_linear_count", *this, &Encoder::set_linear_count, "count")
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
DEFINE_ENUM_FLAG_OPERATORS(Encoder::Error)
|
||||
|
||||
#endif // __ENCODER_HPP
|
||||
|
||||
@@ -11,6 +11,12 @@ class Endstop {
|
||||
bool enabled = false;
|
||||
bool is_active_high = false;
|
||||
bool pullup = true;
|
||||
|
||||
// custom setters
|
||||
Endstop* parent = nullptr
|
||||
void set_gpio_num(uint16_t value) { gpio_num = value; parent->update_config(); }
|
||||
void set_enabled(uint32_t value) { enabled = value; parent->update_config(); }
|
||||
void set_debounce_ms(uint32_t value) { debounce_ms = value; parent->update_config(); }
|
||||
};
|
||||
|
||||
explicit Endstop(Endstop::Config_t& config);
|
||||
@@ -26,21 +32,6 @@ class Endstop {
|
||||
|
||||
bool endstop_state_ = false;
|
||||
|
||||
auto make_protocol_definitions() {
|
||||
return make_protocol_member_list(
|
||||
make_protocol_ro_property("endstop_state", &endstop_state_),
|
||||
make_protocol_object("config",
|
||||
make_protocol_property("gpio_num", &config_.gpio_num,
|
||||
[](void* ctx) { static_cast<Endstop*>(ctx)->update_config(); }, this),
|
||||
make_protocol_property("enabled", &config_.enabled,
|
||||
[](void* ctx) { static_cast<Endstop*>(ctx)->update_config(); }, this),
|
||||
make_protocol_property("offset", &config_.offset),
|
||||
make_protocol_property("is_active_high", &config_.is_active_high),
|
||||
make_protocol_property("pullup", &config_.pullup),
|
||||
make_protocol_property("debounce_ms", &config_.debounce_ms,
|
||||
[](void* ctx) { static_cast<Endstop*>(ctx)->update_config(); }, this)));
|
||||
}
|
||||
|
||||
private:
|
||||
bool pin_state_ = false;
|
||||
float pos_when_pressed_ = 0.0f;
|
||||
|
||||
@@ -613,11 +613,11 @@ void update_brake_current() {
|
||||
}
|
||||
|
||||
// Don't start braking until -Ibus > regen_current_allowed
|
||||
float brake_current = -Ibus_sum - board_config.max_regen_current;
|
||||
float brake_duty = brake_current * board_config.brake_resistance / vbus_voltage;
|
||||
float brake_current = -Ibus_sum - odrv.config_.max_regen_current;
|
||||
float brake_duty = brake_current * odrv.config_.brake_resistance / vbus_voltage;
|
||||
|
||||
if (board_config.enable_dc_bus_overvoltage_ramp && (board_config.brake_resistance > 0.0f) && (board_config.dc_bus_overvoltage_ramp_start < board_config.dc_bus_overvoltage_ramp_end)) {
|
||||
brake_duty += std::fmax((vbus_voltage - board_config.dc_bus_overvoltage_ramp_start) / (board_config.dc_bus_overvoltage_ramp_end - board_config.dc_bus_overvoltage_ramp_start), 0.0f);
|
||||
if (odrv.config_.enable_dc_bus_overvoltage_ramp && (odrv.config_.brake_resistance > 0.0f) && (odrv.config_.dc_bus_overvoltage_ramp_start < odrv.config_.dc_bus_overvoltage_ramp_end)) {
|
||||
brake_duty += std::fmax((vbus_voltage - odrv.config_.dc_bus_overvoltage_ramp_start) / (odrv.config_.dc_bus_overvoltage_ramp_end - odrv.config_.dc_bus_overvoltage_ramp_start), 0.0f);
|
||||
}
|
||||
|
||||
if (std::isnan(brake_duty)) {
|
||||
@@ -634,15 +634,15 @@ void update_brake_current() {
|
||||
brake_duty = std::clamp(brake_duty, 0.0f, 0.95f);
|
||||
|
||||
// Special handling to avoid the case 0.0/0.0 == NaN.
|
||||
Ibus_sum += brake_duty ? (brake_duty * vbus_voltage / board_config.brake_resistance) : 0.0f;
|
||||
Ibus_sum += brake_duty ? (brake_duty * vbus_voltage / odrv.config_.brake_resistance) : 0.0f;
|
||||
|
||||
ibus_ = Ibus_sum;
|
||||
|
||||
if (Ibus_sum > board_config.dc_max_positive_current) {
|
||||
if (Ibus_sum > odrv.config_.dc_max_positive_current) {
|
||||
low_level_fault(Motor::ERROR_DC_BUS_OVER_CURRENT);
|
||||
return;
|
||||
}
|
||||
if (Ibus_sum < board_config.dc_max_negative_current) {
|
||||
if (Ibus_sum < odrv.config_.dc_max_negative_current) {
|
||||
low_level_fault(Motor::ERROR_DC_BUS_OVER_REGEN_CURRENT);
|
||||
return;
|
||||
}
|
||||
@@ -715,7 +715,7 @@ void pwm_in_init() {
|
||||
#else
|
||||
int gpio_num = 4; {
|
||||
#endif
|
||||
if (is_endpoint_ref_valid(board_config.pwm_mappings[gpio_num - 1].endpoint)) {
|
||||
if (fibre::is_endpoint_ref_valid(odrv.config_.pwm_mappings[gpio_num - 1].endpoint)) {
|
||||
GPIO_InitStruct.Pin = get_gpio_pin_by_pin(gpio_num);
|
||||
HAL_GPIO_DeInit(get_gpio_port_by_pin(gpio_num), get_gpio_pin_by_pin(gpio_num));
|
||||
HAL_GPIO_Init(get_gpio_port_by_pin(gpio_num), &GPIO_InitStruct);
|
||||
@@ -742,14 +742,10 @@ void handle_pulse(int gpio_num, uint32_t high_time) {
|
||||
if (high_time > PWM_MAX_HIGH_TIME)
|
||||
high_time = PWM_MAX_HIGH_TIME;
|
||||
float fraction = (float)(high_time - PWM_MIN_HIGH_TIME) / (float)(PWM_MAX_HIGH_TIME - PWM_MIN_HIGH_TIME);
|
||||
float value = board_config.pwm_mappings[gpio_num - 1].min +
|
||||
(fraction * (board_config.pwm_mappings[gpio_num - 1].max - board_config.pwm_mappings[gpio_num - 1].min));
|
||||
float value = odrv.config_.pwm_mappings[gpio_num - 1].min +
|
||||
(fraction * (odrv.config_.pwm_mappings[gpio_num - 1].max - odrv.config_.pwm_mappings[gpio_num - 1].min));
|
||||
|
||||
Endpoint* endpoint = get_endpoint(board_config.pwm_mappings[gpio_num - 1].endpoint);
|
||||
if (!endpoint)
|
||||
return;
|
||||
|
||||
endpoint->set_from_float(value);
|
||||
fibre::set_endpoint_from_float(odrv.config_.pwm_mappings[gpio_num - 1].endpoint, value);
|
||||
}
|
||||
|
||||
void pwm_in_cb(int channel, uint32_t timestamp) {
|
||||
@@ -780,16 +776,16 @@ static void update_analog_endpoint(const struct PWMMapping_t *map, int gpio)
|
||||
{
|
||||
float fraction = get_adc_voltage(get_gpio_port_by_pin(gpio), get_gpio_pin_by_pin(gpio)) / 3.3f;
|
||||
float value = map->min + (fraction * (map->max - map->min));
|
||||
get_endpoint(map->endpoint)->set_from_float(value);
|
||||
fibre::set_endpoint_from_float(map->endpoint, value);
|
||||
}
|
||||
|
||||
static void analog_polling_thread(void *)
|
||||
{
|
||||
while (true) {
|
||||
for (int i = 0; i < GPIO_COUNT; i++) {
|
||||
struct PWMMapping_t *map = &board_config.analog_mappings[i];
|
||||
struct PWMMapping_t *map = &odrv.config_.analog_mappings[i];
|
||||
|
||||
if (is_endpoint_ref_valid(map->endpoint))
|
||||
if (fibre::is_endpoint_ref_valid(map->endpoint))
|
||||
update_analog_endpoint(map, i + 1);
|
||||
}
|
||||
osDelay(10);
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <communication/interface_i2c.h>
|
||||
#include <communication/interface_can.hpp>
|
||||
|
||||
BoardConfig_t board_config;
|
||||
ODriveCAN::Config_t can_config;
|
||||
Encoder::Config_t encoder_configs[AXIS_COUNT];
|
||||
SensorlessEstimator::Config_t sensorless_configs[AXIS_COUNT];
|
||||
@@ -20,12 +19,10 @@ Axis::Config_t axis_configs[AXIS_COUNT];
|
||||
TrapezoidalTrajectory::Config_t trap_configs[AXIS_COUNT];
|
||||
Endstop::Config_t min_endstop_configs[AXIS_COUNT];
|
||||
Endstop::Config_t max_endstop_configs[AXIS_COUNT];
|
||||
bool user_config_loaded_;
|
||||
|
||||
SystemStats_t system_stats_;
|
||||
|
||||
std::array<Axis*, AXIS_COUNT> axes;
|
||||
ODriveCAN *odCAN = nullptr;
|
||||
ODrive odrv{};
|
||||
|
||||
typedef Config<
|
||||
BoardConfig_t,
|
||||
@@ -39,9 +36,9 @@ typedef Config<
|
||||
Endstop::Config_t[AXIS_COUNT],
|
||||
Axis::Config_t[AXIS_COUNT]> ConfigFormat;
|
||||
|
||||
void save_configuration(void) {
|
||||
void ODrive::save_configuration(void) {
|
||||
if (ConfigFormat::safe_store_config(
|
||||
&board_config,
|
||||
&odrv.config_,
|
||||
&can_config,
|
||||
&encoder_configs,
|
||||
&sensorless_configs,
|
||||
@@ -53,7 +50,7 @@ void save_configuration(void) {
|
||||
&axis_configs)) {
|
||||
printf("saving configuration failed\r\n"); osDelay(5);
|
||||
} else {
|
||||
user_config_loaded_ = true;
|
||||
odrv.user_config_loaded_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +58,7 @@ extern "C" int load_configuration(void) {
|
||||
// Try to load configs
|
||||
if (NVM_init() ||
|
||||
ConfigFormat::safe_load_config(
|
||||
&board_config,
|
||||
&odrv.config_,
|
||||
&can_config,
|
||||
&encoder_configs,
|
||||
&sensorless_configs,
|
||||
@@ -72,7 +69,7 @@ extern "C" int load_configuration(void) {
|
||||
&max_endstop_configs,
|
||||
&axis_configs)) {
|
||||
//If loading failed, restore defaults
|
||||
board_config = BoardConfig_t();
|
||||
odrv.config_ = BoardConfig_t();
|
||||
can_config = ODriveCAN::Config_t();
|
||||
for (size_t i = 0; i < AXIS_COUNT; ++i) {
|
||||
encoder_configs[i] = Encoder::Config_t();
|
||||
@@ -89,12 +86,12 @@ extern "C" int load_configuration(void) {
|
||||
controller_configs[i].load_encoder_axis = i;
|
||||
}
|
||||
} else {
|
||||
user_config_loaded_ = true;
|
||||
odrv.user_config_loaded_ = true;
|
||||
}
|
||||
return user_config_loaded_;
|
||||
return odrv.user_config_loaded_;
|
||||
}
|
||||
|
||||
void erase_configuration(void) {
|
||||
void ODrive::erase_configuration(void) {
|
||||
NVM_erase();
|
||||
|
||||
// FIXME: this reboot is a workaround because we don't want the next save_configuration
|
||||
@@ -105,8 +102,8 @@ void erase_configuration(void) {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
void enter_dfu_mode() {
|
||||
if ((hw_version_major == 3) && (hw_version_minor >= 5)) {
|
||||
void ODrive::enter_dfu_mode() {
|
||||
if ((hw_version_major_ == 3) && (hw_version_minor_ >= 5)) {
|
||||
__asm volatile ("CPSID I\n\t":::"memory"); // disable interrupts
|
||||
_reboot_cookie = 0xDEADBEEF;
|
||||
NVIC_SystemReset();
|
||||
@@ -125,7 +122,7 @@ void enter_dfu_mode() {
|
||||
|
||||
extern "C" int construct_objects(){
|
||||
#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3
|
||||
if (board_config.enable_i2c_instead_of_can) {
|
||||
if (odrv.config_.enable_i2c_instead_of_can) {
|
||||
// Set up the direction GPIO as input
|
||||
GPIO_InitTypeDef GPIO_InitStruct;
|
||||
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
|
||||
@@ -149,7 +146,7 @@ extern "C" int construct_objects(){
|
||||
MX_CAN1_Init();
|
||||
|
||||
HAL_UART_DeInit(&huart4);
|
||||
huart4.Init.BaudRate = board_config.uart_baudrate;
|
||||
huart4.Init.BaudRate = odrv.config_.uart_baudrate;
|
||||
HAL_UART_Init(&huart4);
|
||||
|
||||
// Init general user ADC on some GPIOs.
|
||||
@@ -170,7 +167,7 @@ extern "C" int construct_objects(){
|
||||
#endif
|
||||
|
||||
// Construct all objects.
|
||||
odCAN = new ODriveCAN(&hcan1, can_config);
|
||||
odCAN = new ODriveCAN(can_config, &hcan1);
|
||||
for (size_t i = 0; i < AXIS_COUNT; ++i) {
|
||||
Encoder *encoder = new Encoder(hw_configs[i].encoder_config,
|
||||
encoder_configs[i], motor_configs[i]);
|
||||
@@ -184,8 +181,14 @@ extern "C" int construct_objects(){
|
||||
Endstop *max_endstop = new Endstop(max_endstop_configs[i]);
|
||||
axes[i] = new Axis(i, hw_configs[i].axis_config, axis_configs[i],
|
||||
*encoder, *sensorless_estimator, *controller, *motor, *trap, *min_endstop, *max_endstop);
|
||||
|
||||
controller_configs[i].parent = controller;
|
||||
encoder_configs[i].parent = encoder;
|
||||
motor_configs[i].parent = motor;
|
||||
min_endstop_configs[i].parent = min_endstop;
|
||||
max_endstop_configs[i].parent = max_endstop;
|
||||
axis_configs[i].parent = axes[i];
|
||||
}
|
||||
initTree();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -199,27 +202,27 @@ void vApplicationStackOverflowHook(xTaskHandle *pxTask, signed portCHAR *pcTaskN
|
||||
for (;;); // TODO: safe action
|
||||
}
|
||||
void vApplicationIdleHook(void) {
|
||||
if (system_stats_.fully_booted) {
|
||||
system_stats_.uptime = xTaskGetTickCount();
|
||||
system_stats_.min_heap_space = xPortGetMinimumEverFreeHeapSize();
|
||||
system_stats_.min_stack_space_comms = uxTaskGetStackHighWaterMark(comm_thread) * sizeof(StackType_t);
|
||||
system_stats_.min_stack_space_axis0 = uxTaskGetStackHighWaterMark(axes[0]->thread_id_) * sizeof(StackType_t);
|
||||
system_stats_.min_stack_space_axis1 = uxTaskGetStackHighWaterMark(axes[1]->thread_id_) * sizeof(StackType_t);
|
||||
system_stats_.min_stack_space_usb = uxTaskGetStackHighWaterMark(usb_thread) * sizeof(StackType_t);
|
||||
system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t);
|
||||
system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread) * sizeof(StackType_t);
|
||||
system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle) * sizeof(StackType_t);
|
||||
system_stats_.min_stack_space_can = uxTaskGetStackHighWaterMark(odCAN->thread_id_) * sizeof(StackType_t);
|
||||
if (odrv.system_stats_.fully_booted) {
|
||||
odrv.system_stats_.uptime = xTaskGetTickCount();
|
||||
odrv.system_stats_.min_heap_space = xPortGetMinimumEverFreeHeapSize();
|
||||
odrv.system_stats_.min_stack_space_comms = uxTaskGetStackHighWaterMark(comm_thread) * sizeof(StackType_t);
|
||||
odrv.system_stats_.min_stack_space_axis0 = uxTaskGetStackHighWaterMark(axes[0]->thread_id_) * sizeof(StackType_t);
|
||||
odrv.system_stats_.min_stack_space_axis1 = uxTaskGetStackHighWaterMark(axes[1]->thread_id_) * sizeof(StackType_t);
|
||||
odrv.system_stats_.min_stack_space_usb = uxTaskGetStackHighWaterMark(usb_thread) * sizeof(StackType_t);
|
||||
odrv.system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t);
|
||||
odrv.system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread) * sizeof(StackType_t);
|
||||
odrv.system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle) * sizeof(StackType_t);
|
||||
odrv.system_stats_.min_stack_space_can = uxTaskGetStackHighWaterMark(odCAN->thread_id_) * sizeof(StackType_t);
|
||||
|
||||
// Actual usage, in bytes, so we don't have to math
|
||||
system_stats_.stack_usage_axis0 = axes[0]->stack_size_ - system_stats_.min_stack_space_axis0;
|
||||
system_stats_.stack_usage_axis1 = axes[1]->stack_size_ - system_stats_.min_stack_space_axis1;
|
||||
system_stats_.stack_usage_comms = stack_size_comm_thread - system_stats_.min_stack_space_comms;
|
||||
system_stats_.stack_usage_usb = stack_size_usb_thread - system_stats_.min_stack_space_usb;
|
||||
system_stats_.stack_usage_uart = stack_size_uart_thread - system_stats_.min_stack_space_uart;
|
||||
system_stats_.stack_usage_usb_irq = stack_size_usb_irq_thread - system_stats_.min_stack_space_usb_irq;
|
||||
system_stats_.stack_usage_startup = stack_size_default_task - system_stats_.min_stack_space_startup;
|
||||
system_stats_.stack_usage_can = odCAN->stack_size_ - system_stats_.min_stack_space_can;
|
||||
odrv.system_stats_.stack_usage_axis0 = axes[0]->stack_size_ - odrv.system_stats_.min_stack_space_axis0;
|
||||
odrv.system_stats_.stack_usage_axis1 = axes[1]->stack_size_ - odrv.system_stats_.min_stack_space_axis1;
|
||||
odrv.system_stats_.stack_usage_comms = stack_size_comm_thread - odrv.system_stats_.min_stack_space_comms;
|
||||
odrv.system_stats_.stack_usage_usb = stack_size_usb_thread - odrv.system_stats_.min_stack_space_usb;
|
||||
odrv.system_stats_.stack_usage_uart = stack_size_uart_thread - odrv.system_stats_.min_stack_space_uart;
|
||||
odrv.system_stats_.stack_usage_usb_irq = stack_size_usb_irq_thread - odrv.system_stats_.min_stack_space_usb_irq;
|
||||
odrv.system_stats_.stack_usage_startup = stack_size_default_task - odrv.system_stats_.min_stack_space_startup;
|
||||
odrv.system_stats_.stack_usage_can = odCAN->stack_size_ - odrv.system_stats_.min_stack_space_can;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,7 +233,7 @@ int odrive_main(void) {
|
||||
|
||||
// TODO: make dynamically reconfigurable
|
||||
#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3
|
||||
if (board_config.enable_uart) {
|
||||
if (odrv.config_.enable_uart) {
|
||||
SetGPIO12toUART();
|
||||
}
|
||||
#endif
|
||||
@@ -271,6 +274,6 @@ int odrive_main(void) {
|
||||
|
||||
start_analog_thread();
|
||||
|
||||
system_stats_.fully_booted = true;
|
||||
odrv.system_stats_.fully_booted = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ bool Motor::check_DRV_fault() {
|
||||
GPIO_PinState nFAULT_state = HAL_GPIO_ReadPin(gate_driver_config_.nFAULT_port, gate_driver_config_.nFAULT_pin);
|
||||
if (nFAULT_state == GPIO_PIN_RESET) {
|
||||
// Update DRV Fault Code
|
||||
drv_fault_ = DRV8301_getFaultType(&gate_driver_);
|
||||
gate_driver_exported_.drv_fault = (GateDriverIntf::DrvFault)DRV8301_getFaultType(&gate_driver_);
|
||||
// Update/Cache all SPI device registers
|
||||
// DRV_SPI_8301_Vars_t* local_regs = &gate_driver_regs_;
|
||||
// local_regs->RcvCmd = true;
|
||||
|
||||
+22
-125
@@ -7,35 +7,8 @@
|
||||
|
||||
#include "drv8301.h"
|
||||
|
||||
class Motor {
|
||||
class Motor : public MotorIntf {
|
||||
public:
|
||||
enum Error {
|
||||
ERROR_NONE = 0,
|
||||
ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x0001,
|
||||
ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x0002,
|
||||
ERROR_ADC_FAILED = 0x0004,
|
||||
ERROR_DRV_FAULT = 0x0008,
|
||||
ERROR_CONTROL_DEADLINE_MISSED = 0x0010,
|
||||
ERROR_NOT_IMPLEMENTED_MOTOR_TYPE = 0x0020,
|
||||
ERROR_BRAKE_CURRENT_OUT_OF_RANGE = 0x0040,
|
||||
ERROR_MODULATION_MAGNITUDE = 0x0080,
|
||||
ERROR_BRAKE_DEADTIME_VIOLATION = 0x0100,
|
||||
ERROR_UNEXPECTED_TIMER_CALLBACK = 0x0200,
|
||||
ERROR_CURRENT_SENSE_SATURATION = 0x0400,
|
||||
ERROR_INVERTER_OVER_TEMP = 0x0800,
|
||||
ERROR_CURRENT_LIMIT_VIOLATION = 0x1000,
|
||||
ERROR_BRAKE_DUTY_CYCLE_NAN = 0x2000,
|
||||
ERROR_DC_BUS_OVER_REGEN_CURRENT = 0x4000, // too much current pushed into the power supply
|
||||
ERROR_DC_BUS_OVER_CURRENT = 0x8000, // too much current pulled out of the power supply
|
||||
};
|
||||
|
||||
enum MotorType_t {
|
||||
MOTOR_TYPE_HIGH_CURRENT = 0,
|
||||
// MOTOR_TYPE_LOW_CURRENT = 1, //Not yet implemented
|
||||
MOTOR_TYPE_GIMBAL = 2,
|
||||
MOTOR_TYPE_ACIM = 3,
|
||||
};
|
||||
|
||||
struct Iph_BC_t {
|
||||
float phB;
|
||||
float phC;
|
||||
@@ -65,7 +38,7 @@ public:
|
||||
// NOTE: for gimbal motors, all units of A are instead V.
|
||||
// example: vel_gain is [V/(count/s)] instead of [A/(count/s)]
|
||||
// example: current_lim and calibration_current will instead determine the maximum voltage applied to the motor.
|
||||
struct Config_t {
|
||||
struct Config_t : public ConfigIntf {
|
||||
bool pre_calibrated = false; // can be set to true to indicate that all values here are valid
|
||||
int32_t pole_pairs = 7;
|
||||
float calibration_current = 10.0f; // [A]
|
||||
@@ -73,7 +46,7 @@ public:
|
||||
float phase_inductance = 0.0f; // to be set by measure_phase_inductance
|
||||
float phase_resistance = 0.0f; // to be set by measure_phase_resistance
|
||||
int32_t direction = 0; // 1 or -1 (0 = unspecified)
|
||||
MotorType_t motor_type = MOTOR_TYPE_HIGH_CURRENT;
|
||||
MotorType motor_type = MOTOR_TYPE_HIGH_CURRENT;
|
||||
// Read out max_allowed_current to see max supported value for current_lim.
|
||||
// float current_lim = 70.0f; //[A]
|
||||
float current_lim = 10.0f; //[A]
|
||||
@@ -89,6 +62,16 @@ public:
|
||||
bool acim_autoflux_enable = false;
|
||||
float acim_autoflux_attack_gain = 10.0f;
|
||||
float acim_autoflux_decay_gain = 1.0f;
|
||||
|
||||
// custom property setters
|
||||
Motor* parent = nullptr;
|
||||
void set_pre_calibrated(bool value) {
|
||||
pre_calibrated = value;
|
||||
parent->is_calibrated_ = parent->is_calibrated_ || parent->config_.pre_calibrated;
|
||||
}
|
||||
void set_phase_inductance(float value) { phase_inductance = value; parent->update_current_controller_gains(); }
|
||||
void set_phase_resistance(float value) { phase_resistance = value; parent->update_current_controller_gains(); }
|
||||
void set_current_control_bandwidth(float value) { current_control_bandwidth = value; parent->update_current_controller_gains(); }
|
||||
};
|
||||
|
||||
enum TimingLog_t {
|
||||
@@ -107,13 +90,6 @@ public:
|
||||
TIMING_LOG_NUM_SLOTS
|
||||
};
|
||||
|
||||
enum ArmedState_t {
|
||||
ARMED_STATE_DISARMED,
|
||||
ARMED_STATE_WAITING_FOR_TIMINGS,
|
||||
ARMED_STATE_WAITING_FOR_UPDATE,
|
||||
ARMED_STATE_ARMED,
|
||||
};
|
||||
|
||||
Motor(const MotorHardwareConfig_t& hw_config,
|
||||
const GateDriverHardwareConfig_t& gate_driver_config,
|
||||
Config_t& config);
|
||||
@@ -160,13 +136,17 @@ public:
|
||||
bool next_timings_valid_ = false;
|
||||
uint16_t last_cpu_time_ = 0;
|
||||
int timing_log_index_ = 0;
|
||||
uint16_t timing_log_[TIMING_LOG_NUM_SLOTS] = { 0 };
|
||||
struct {
|
||||
uint16_t& operator[](size_t idx) { return content[idx]; }
|
||||
uint16_t& get(size_t idx) { return content[idx]; }
|
||||
uint16_t content[TIMING_LOG_NUM_SLOTS];
|
||||
} timing_log_;
|
||||
|
||||
// variables exposed on protocol
|
||||
Error error_ = ERROR_NONE;
|
||||
// Do not write to this variable directly!
|
||||
// It is for exclusive use by the safety_critical_... functions.
|
||||
ArmedState_t armed_state_ = ARMED_STATE_DISARMED;
|
||||
ArmedState armed_state_ = ARMED_STATE_DISARMED;
|
||||
bool is_calibrated_ = config_.pre_calibrated;
|
||||
Iph_BC_t current_meas_ = {0.0f, 0.0f};
|
||||
Iph_BC_t DC_calib_ = {0.0f, 0.0f};
|
||||
@@ -190,95 +170,12 @@ public:
|
||||
.async_phase_vel = 0.0f,
|
||||
.async_phase_offset = 0.0f,
|
||||
};
|
||||
DRV8301_FaultType_e drv_fault_ = DRV8301_FaultType_NoFault;
|
||||
struct : GateDriverIntf {
|
||||
DrvFault drv_fault = DRV_FAULT_NO_FAULT;
|
||||
} gate_driver_exported_;
|
||||
DRV_SPI_8301_Vars_t gate_driver_regs_; //Local view of DRV registers (initialized by DRV8301_setup)
|
||||
float thermal_current_lim_ = 10.0f; //[A]
|
||||
float inverter_temp_ = 20.0f;
|
||||
|
||||
// Communication protocol definitions
|
||||
auto make_protocol_definitions() {
|
||||
return make_protocol_member_list(
|
||||
make_protocol_property("error", &error_),
|
||||
make_protocol_ro_property("armed_state", &armed_state_),
|
||||
make_protocol_ro_property("is_calibrated", &is_calibrated_),
|
||||
make_protocol_ro_property("current_meas_phB", ¤t_meas_.phB),
|
||||
make_protocol_ro_property("current_meas_phC", ¤t_meas_.phC),
|
||||
make_protocol_property("DC_calib_phB", &DC_calib_.phB),
|
||||
make_protocol_property("DC_calib_phC", &DC_calib_.phC),
|
||||
make_protocol_property("phase_current_rev_gain", &phase_current_rev_gain_),
|
||||
make_protocol_ro_property("thermal_current_lim", &thermal_current_lim_),
|
||||
make_protocol_ro_property("inverter_temp", &inverter_temp_),
|
||||
make_protocol_object("current_control",
|
||||
make_protocol_property("p_gain", ¤t_control_.p_gain),
|
||||
make_protocol_property("i_gain", ¤t_control_.i_gain),
|
||||
make_protocol_property("v_current_control_integral_d", ¤t_control_.v_current_control_integral_d),
|
||||
make_protocol_property("v_current_control_integral_q", ¤t_control_.v_current_control_integral_q),
|
||||
make_protocol_property("Ibus", ¤t_control_.Ibus),
|
||||
make_protocol_property("final_v_alpha", ¤t_control_.final_v_alpha),
|
||||
make_protocol_property("final_v_beta", ¤t_control_.final_v_beta),
|
||||
make_protocol_property("Id_setpoint", ¤t_control_.Id_setpoint),
|
||||
make_protocol_ro_property("Iq_setpoint", ¤t_control_.Iq_setpoint),
|
||||
make_protocol_property("Iq_measured", ¤t_control_.Iq_measured),
|
||||
make_protocol_property("Id_measured", ¤t_control_.Id_measured),
|
||||
make_protocol_property("I_measured_report_filter_k", ¤t_control_.I_measured_report_filter_k),
|
||||
make_protocol_ro_property("max_allowed_current", ¤t_control_.max_allowed_current),
|
||||
make_protocol_ro_property("overcurrent_trip_level", ¤t_control_.overcurrent_trip_level),
|
||||
make_protocol_property("acim_rotor_flux", ¤t_control_.acim_rotor_flux),
|
||||
make_protocol_ro_property("async_phase_vel", ¤t_control_.async_phase_vel),
|
||||
make_protocol_property("async_phase_offset", ¤t_control_.async_phase_offset)
|
||||
),
|
||||
make_protocol_object("gate_driver",
|
||||
make_protocol_ro_property("drv_fault", &drv_fault_)
|
||||
// make_protocol_ro_property("status_reg_1", &gate_driver_regs_.Stat_Reg_1_Value),
|
||||
// make_protocol_ro_property("status_reg_2", &gate_driver_regs_.Stat_Reg_2_Value),
|
||||
// make_protocol_ro_property("ctrl_reg_1", &gate_driver_regs_.Ctrl_Reg_1_Value),
|
||||
// make_protocol_ro_property("ctrl_reg_2", &gate_driver_regs_.Ctrl_Reg_2_Value)
|
||||
),
|
||||
make_protocol_object("timing_log",
|
||||
make_protocol_ro_property("TIMING_LOG_GENERAL", &timing_log_[TIMING_LOG_GENERAL]),
|
||||
make_protocol_ro_property("TIMING_LOG_ADC_CB_I", &timing_log_[TIMING_LOG_ADC_CB_I]),
|
||||
make_protocol_ro_property("TIMING_LOG_ADC_CB_DC", &timing_log_[TIMING_LOG_ADC_CB_DC]),
|
||||
make_protocol_ro_property("TIMING_LOG_MEAS_R", &timing_log_[TIMING_LOG_MEAS_R]),
|
||||
make_protocol_ro_property("TIMING_LOG_MEAS_L", &timing_log_[TIMING_LOG_MEAS_L]),
|
||||
make_protocol_ro_property("TIMING_LOG_ENC_CALIB", &timing_log_[TIMING_LOG_ENC_CALIB]),
|
||||
make_protocol_ro_property("TIMING_LOG_IDX_SEARCH", &timing_log_[TIMING_LOG_IDX_SEARCH]),
|
||||
make_protocol_ro_property("TIMING_LOG_FOC_VOLTAGE", &timing_log_[TIMING_LOG_FOC_VOLTAGE]),
|
||||
make_protocol_ro_property("TIMING_LOG_FOC_CURRENT", &timing_log_[TIMING_LOG_FOC_CURRENT]),
|
||||
make_protocol_ro_property("TIMING_LOG_SPI_START", &timing_log_[TIMING_LOG_SPI_START]),
|
||||
make_protocol_ro_property("TIMING_LOG_SAMPLE_NOW", &timing_log_[TIMING_LOG_SAMPLE_NOW]),
|
||||
make_protocol_ro_property("TIMING_LOG_SPI_END", &timing_log_[TIMING_LOG_SPI_END])
|
||||
),
|
||||
make_protocol_object("config",
|
||||
make_protocol_property("pre_calibrated", &config_.pre_calibrated,
|
||||
[](void* ctx) { static_cast<Motor*>(ctx)->is_calibrated_ =
|
||||
static_cast<Motor*>(ctx)->is_calibrated_ || static_cast<Motor*>(ctx)->config_.pre_calibrated; }, this),
|
||||
make_protocol_property("pole_pairs", &config_.pole_pairs),
|
||||
make_protocol_property("calibration_current", &config_.calibration_current),
|
||||
make_protocol_property("resistance_calib_max_voltage", &config_.resistance_calib_max_voltage),
|
||||
make_protocol_property("phase_inductance", &config_.phase_inductance,
|
||||
[](void* ctx) { static_cast<Motor*>(ctx)->update_current_controller_gains(); }, this),
|
||||
make_protocol_property("phase_resistance", &config_.phase_resistance,
|
||||
[](void* ctx) { static_cast<Motor*>(ctx)->update_current_controller_gains(); }, this),
|
||||
make_protocol_property("direction", &config_.direction),
|
||||
make_protocol_property("motor_type", &config_.motor_type),
|
||||
make_protocol_property("current_lim", &config_.current_lim),
|
||||
make_protocol_property("current_lim_margin", &config_.current_lim_margin),
|
||||
make_protocol_property("inverter_temp_limit_lower", &config_.inverter_temp_limit_lower),
|
||||
make_protocol_property("inverter_temp_limit_upper", &config_.inverter_temp_limit_upper),
|
||||
make_protocol_property("requested_current_range", &config_.requested_current_range),
|
||||
make_protocol_property("current_control_bandwidth", &config_.current_control_bandwidth,
|
||||
[](void* ctx) { static_cast<Motor*>(ctx)->update_current_controller_gains(); }, this),
|
||||
make_protocol_property("acim_slip_velocity", &config_.acim_slip_velocity),
|
||||
make_protocol_property("acim_gain_min_flux", &config_.acim_gain_min_flux),
|
||||
make_protocol_property("acim_autoflux_min_Id", &config_.acim_autoflux_min_Id),
|
||||
make_protocol_property("acim_autoflux_enable", &config_.acim_autoflux_enable),
|
||||
make_protocol_property("acim_autoflux_attack_gain", &config_.acim_autoflux_attack_gain),
|
||||
make_protocol_property("acim_autoflux_decay_gain", &config_.acim_autoflux_decay_gain)
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
DEFINE_ENUM_FLAG_OPERATORS(Motor::Error)
|
||||
|
||||
#endif // __MOTOR_HPP
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <fibre/protocol.hpp>
|
||||
#include <communication/interface_usb.h>
|
||||
#include <communication/interface_i2c.h>
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
@@ -41,11 +43,13 @@ static const int current_meas_hz = CURRENT_MEAS_HZ;
|
||||
|
||||
// extern const float elec_rad_per_enc;
|
||||
extern uint32_t _reboot_cookie;
|
||||
extern bool user_config_loaded_;
|
||||
|
||||
extern uint64_t serial_number;
|
||||
extern char serial_number_str[13];
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
bool fully_booted;
|
||||
uint32_t uptime; // [ms]
|
||||
@@ -67,11 +71,10 @@ typedef struct {
|
||||
uint32_t stack_usage_usb_irq;
|
||||
uint32_t stack_usage_startup;
|
||||
uint32_t stack_usage_can;
|
||||
} SystemStats_t;
|
||||
extern SystemStats_t system_stats_;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
USBStats_t& usb = usb_stats_;
|
||||
I2CStats_t& i2c = i2c_stats_;
|
||||
} SystemStats_t;
|
||||
|
||||
struct PWMMapping_t {
|
||||
endpoint_ref_t endpoint;
|
||||
@@ -148,8 +151,6 @@ struct BoardConfig_t {
|
||||
*/
|
||||
uint32_t uart_baudrate = 115200;
|
||||
};
|
||||
extern BoardConfig_t board_config;
|
||||
extern bool user_config_loaded_;
|
||||
|
||||
// Forward Declarations
|
||||
class Axis;
|
||||
@@ -176,6 +177,7 @@ inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b) { return reinterpret_cast
|
||||
inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b) { return reinterpret_cast<ENUMTYPE&>(reinterpret_cast<std::underlying_type_t<ENUMTYPE>&>(a) ^= static_cast<std::underlying_type_t<ENUMTYPE>>(b)); } \
|
||||
inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast<ENUMTYPE>(~static_cast<std::underlying_type_t<ENUMTYPE>>(a)); }
|
||||
|
||||
#include "autogen/interfaces.hpp"
|
||||
|
||||
// ODrive specific includes
|
||||
#include <utils.hpp>
|
||||
@@ -190,12 +192,79 @@ inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast<ENUMTYPE>(~static_c
|
||||
#include <axis.hpp>
|
||||
#include <communication/communication.h>
|
||||
|
||||
#endif // __cplusplus
|
||||
#include "autogen/version.h"
|
||||
|
||||
|
||||
// general system functions defined in main.cpp
|
||||
void save_configuration(void);
|
||||
void erase_configuration(void);
|
||||
void enter_dfu_mode(void);
|
||||
class ODrive : public OdriveIntf {
|
||||
public:
|
||||
void save_configuration() override;
|
||||
void erase_configuration() override;
|
||||
void reboot() override { NVIC_SystemReset(); }
|
||||
void enter_dfu_mode() override;
|
||||
|
||||
float get_oscilloscope_val(uint32_t index) override {
|
||||
return oscilloscope[index];
|
||||
}
|
||||
|
||||
float get_adc_voltage(uint32_t gpio) override {
|
||||
return ::get_adc_voltage(get_gpio_port_by_pin(gpio), get_gpio_pin_by_pin(gpio));
|
||||
}
|
||||
|
||||
int32_t test_function(int32_t delta) override {
|
||||
static int cnt = 0;
|
||||
return cnt += delta;
|
||||
}
|
||||
|
||||
Axis& get_axis(int num) { return *axes[num]; }
|
||||
ODriveCAN& get_can() { return *odCAN; }
|
||||
|
||||
float& vbus_voltage_ = ::vbus_voltage; // TODO: make this the actual variable
|
||||
float& ibus_ = ::ibus_; // TODO: make this the actual variable
|
||||
|
||||
const uint64_t& serial_number_ = ::serial_number;
|
||||
|
||||
#if HW_VERSION_MAJOR == 3
|
||||
// Determine start address of the OTP struct:
|
||||
// The OTP is organized into 16-byte blocks.
|
||||
// If the first block starts with "0xfe" we use the first block.
|
||||
// If the first block starts with "0x00" and the second block starts with "0xfe",
|
||||
// we use the second block. This gives the user the chance to screw up once.
|
||||
// If none of the above is the case, we consider the OTP invalid (otp_ptr will be NULL).
|
||||
const uint8_t* otp_ptr =
|
||||
(*(uint8_t*)FLASH_OTP_BASE == 0xfe) ? (uint8_t*)FLASH_OTP_BASE :
|
||||
(*(uint8_t*)FLASH_OTP_BASE != 0x00) ? NULL :
|
||||
(*(uint8_t*)(FLASH_OTP_BASE + 0x10) != 0xfe) ? NULL :
|
||||
(uint8_t*)(FLASH_OTP_BASE + 0x10);
|
||||
|
||||
// Read hardware version from OTP if available, otherwise fall back
|
||||
// to software defined version.
|
||||
const uint8_t hw_version_major_ = otp_ptr ? otp_ptr[3] : HW_VERSION_MAJOR;
|
||||
const uint8_t hw_version_minor_ = otp_ptr ? otp_ptr[4] : HW_VERSION_MINOR;
|
||||
const uint8_t hw_version_variant_ = otp_ptr ? otp_ptr[5] : HW_VERSION_VOLTAGE;
|
||||
#else
|
||||
#error "not implemented"
|
||||
#endif
|
||||
|
||||
// the corresponding macros are defined in the autogenerated version.h
|
||||
const uint8_t fw_version_major_ = FW_VERSION_MAJOR;
|
||||
const uint8_t fw_version_minor_ = FW_VERSION_MINOR;
|
||||
const uint8_t fw_version_revision_ = FW_VERSION_REVISION;
|
||||
const uint8_t fw_version_unreleased_ = FW_VERSION_UNRELEASED; // 0 for official releases, 1 otherwise
|
||||
|
||||
bool& brake_resistor_armed_ = ::brake_resistor_armed; // TODO: make this the actual variable
|
||||
bool& brake_resistor_saturated_ = ::brake_resistor_saturated; // TODO: make this the actual variable
|
||||
|
||||
SystemStats_t system_stats_;
|
||||
|
||||
BoardConfig_t config_;
|
||||
bool user_config_loaded_;
|
||||
|
||||
uint32_t test_property_ = 0;
|
||||
};
|
||||
|
||||
extern ODrive odrv; // defined in main.cpp
|
||||
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif /* __ODRIVE_MAIN_H */
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
#ifndef __SENSORLESS_ESTIMATOR_HPP
|
||||
#define __SENSORLESS_ESTIMATOR_HPP
|
||||
|
||||
class SensorlessEstimator {
|
||||
class SensorlessEstimator : public SensorlessEstimatorIntf {
|
||||
public:
|
||||
enum Error {
|
||||
ERROR_NONE = 0,
|
||||
ERROR_UNSTABLE_GAIN = 0x01,
|
||||
};
|
||||
|
||||
struct Config_t {
|
||||
float observer_gain = 1000.0f; // [rad/s]
|
||||
float pll_bandwidth = 1000.0f; // [rad/s]
|
||||
@@ -32,25 +27,6 @@ public:
|
||||
float flux_state_[2] = {0.0f, 0.0f}; // [Vs]
|
||||
float V_alpha_beta_memory_[2] = {0.0f, 0.0f}; // [V]
|
||||
bool estimator_good_ = false;
|
||||
|
||||
// Communication protocol definitions
|
||||
auto make_protocol_definitions() {
|
||||
return make_protocol_member_list(
|
||||
make_protocol_property("error", &error_),
|
||||
make_protocol_property("phase", &phase_),
|
||||
make_protocol_property("pll_pos", &pll_pos_),
|
||||
make_protocol_property("vel_estimate", &vel_estimate_),
|
||||
// make_protocol_property("pll_kp", &pll_kp_),
|
||||
// make_protocol_property("pll_ki", &pll_ki_),
|
||||
make_protocol_object("config",
|
||||
make_protocol_property("observer_gain", &config_.observer_gain),
|
||||
make_protocol_property("pll_bandwidth", &config_.pll_bandwidth),
|
||||
make_protocol_property("pm_flux_linkage", &config_.pm_flux_linkage)
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
DEFINE_ENUM_FLAG_OPERATORS(SensorlessEstimator::Error)
|
||||
|
||||
#endif /* __SENSORLESS_ESTIMATOR_HPP */
|
||||
|
||||
@@ -20,16 +20,6 @@ public:
|
||||
float Vmax, float Amax, float Dmax);
|
||||
Step_t eval(float t);
|
||||
|
||||
auto make_protocol_definitions() {
|
||||
return make_protocol_member_list(
|
||||
make_protocol_object("config",
|
||||
make_protocol_property("vel_limit", &config_.vel_limit),
|
||||
make_protocol_property("accel_limit", &config_.accel_limit),
|
||||
make_protocol_property("decel_limit", &config_.decel_limit)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Axis* axis_ = nullptr; // set by Axis constructor
|
||||
Config_t& config_;
|
||||
|
||||
|
||||
+11
-5
@@ -1,6 +1,17 @@
|
||||
|
||||
tup.include('build.lua')
|
||||
|
||||
tup.frule{inputs={'fibre/cpp/interfaces_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/interfaces.hpp'}
|
||||
tup.frule{inputs={'fibre/cpp/function_stubs_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/function_stubs.hpp'}
|
||||
tup.frule{inputs={'fibre/cpp/endpoints_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/endpoints.hpp'}
|
||||
tup.frule{inputs={'ascii_type_info_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/ascii_type_info.hpp'}
|
||||
|
||||
tup.frule{
|
||||
command='python ../tools/odrive/version.py --output %o',
|
||||
outputs={'autogen/version.h'}
|
||||
}
|
||||
|
||||
|
||||
-- Switch between board versions
|
||||
boardversion = tup.getconfig("BOARD_VERSION")
|
||||
if boardversion == "v3.1" then
|
||||
@@ -86,7 +97,6 @@ if tup.getconfig("STRICT") == "true" then
|
||||
FLAGS += '-Werror'
|
||||
end
|
||||
|
||||
|
||||
-- C-specific flags
|
||||
FLAGS += '-D__weak="__attribute__((weak))"'
|
||||
FLAGS += '-D__packed="__attribute__((__packed__))"'
|
||||
@@ -145,10 +155,6 @@ build{
|
||||
includes=stm_includes
|
||||
}
|
||||
|
||||
tup.frule{
|
||||
command='python ../tools/odrive/version.py --output %o',
|
||||
outputs={'build/version.h'}
|
||||
}
|
||||
|
||||
build{
|
||||
name='ODriveFirmware',
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*[# This is the original template, thus the warning below does not apply to this file #]
|
||||
* ============================ WARNING ============================
|
||||
* ==== This is an autogenerated file. ====
|
||||
* ==== Any changes to this file will be lost when recompiling. ====
|
||||
* =================================================================
|
||||
*
|
||||
* This file contains support functions for the ODrive ASCII protocol.
|
||||
*
|
||||
* TODO: might generalize this as an approach to runtime introspection.
|
||||
*/
|
||||
|
||||
|
||||
class TypeInfo;
|
||||
|
||||
struct PropertyInfo {
|
||||
const char * name;
|
||||
void*(*getter)(void*);
|
||||
TypeInfo* type_info;
|
||||
};
|
||||
|
||||
class TypeInfo {
|
||||
public:
|
||||
TypeInfo(const PropertyInfo* property_table, size_t property_table_length)
|
||||
: property_table_(property_table), property_table_length_(property_table_length) {}
|
||||
|
||||
//virtual bool read_string(void* ctx) { return false; };
|
||||
//virtual bool write_string(void* ctx) { return false; };
|
||||
|
||||
const PropertyInfo* get_property_info(const char * name, size_t length) {
|
||||
for (const PropertyInfo* prop = property_table_; prop < (property_table_ + property_table_length_); ++prop) {
|
||||
if (!strncmp(name, prop->name, length)) {
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
const PropertyInfo* property_table_;
|
||||
size_t property_table_length_;
|
||||
};
|
||||
|
||||
|
||||
class Introspectable {
|
||||
public:
|
||||
Introspectable(void* obj, TypeInfo* type_info) : obj_(obj), type_info_(type_info) {}
|
||||
|
||||
Introspectable get_child(const char * path, size_t length) {
|
||||
Introspectable current = *this;
|
||||
|
||||
const char * begin = path;
|
||||
const char * end = path + length;
|
||||
|
||||
while ((begin < end) && current.obj_ && current.type_info_) {
|
||||
const char * end_of_token = std::find(begin, end, '.');
|
||||
const PropertyInfo* prop_info = current.type_info_->get_property_info(begin, end_of_token - begin);
|
||||
if (prop_info) {
|
||||
current = Introspectable{(*prop_info->getter)(obj_), prop_info->type_info};
|
||||
} else {
|
||||
current = Introspectable{nullptr, nullptr};
|
||||
}
|
||||
begin = std::min(end, end_of_token + 1);
|
||||
}
|
||||
|
||||
return current;
|
||||
};
|
||||
|
||||
private:
|
||||
void* obj_;
|
||||
TypeInfo* type_info_;
|
||||
};
|
||||
|
||||
[% for intf in interfaces.values() %]
|
||||
|
||||
template<typename T>
|
||||
struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo {
|
||||
static const PropertyInfo property_table[];
|
||||
static const TypeInfo singleton;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
const PropertyInfo [[intf.name | to_pascal_case]]TypeInfo<T>::property_table[] = {
|
||||
[%- for property in intf.attributes.values() %]
|
||||
{"[[property.name]]", [](void* obj){ return (void*)static_cast<[[property.type.c_type]]*>(&((T*)obj)->[[property.name | to_snake_case]]); }, [[property.type.fullname | to_pascal_case]]TypeInfo<decltype(std::declval<T>().[[property.name | to_snake_case]])>::singleton},
|
||||
[%- endfor %]
|
||||
};
|
||||
template<typename T>
|
||||
const TypeInfo [[intf.fullname | to_pascal_case]]TypeInfo<T>::singleton{[[intf.fullname | to_pascal_case]]TypeInfo<T>::property_table, sizeof([[intf.fullname | to_pascal_case]]TypeInfo<T>::property_table) / sizeof([[intf.fullname | to_pascal_case]]TypeInfo<T>::property_table[0])};
|
||||
|
||||
[% endfor %]
|
||||
+1
-1
@@ -72,7 +72,7 @@ function GCCToolchain(prefix, builddir, compiler_flags, linker_flags)
|
||||
else
|
||||
extra_outputs = {}
|
||||
end
|
||||
if src == 'communication/communication.cpp' then extra_inputs = 'build/version.h' end -- TODO: fix hack
|
||||
extra_inputs = {'autogen/version.h', 'autogen/interfaces.hpp', 'autogen/function_stubs.hpp', 'autogen/endpoints.hpp'} -- TODO: fix hack
|
||||
tup.frule{
|
||||
inputs= { src, extra_inputs=extra_inputs },
|
||||
command=compiler..' -c %f '..
|
||||
|
||||
@@ -8,12 +8,15 @@
|
||||
/* Includes ------------------------------------------------------------------*/
|
||||
|
||||
#include "odrive_main.h"
|
||||
#include "../build/version.h" // autogenerated based on Git state
|
||||
#include "../autogen/version.h" // autogenerated based on Git state
|
||||
#include "communication.h"
|
||||
#include "ascii_protocol.hpp"
|
||||
#include <utils.hpp>
|
||||
#include <fibre/cpp_utils.hpp>
|
||||
|
||||
//#include "autogen/interfaces.hpp"
|
||||
//#include "autogen/ascii_type_info.hpp"
|
||||
|
||||
/* Private macros ------------------------------------------------------------*/
|
||||
/* Private typedef -----------------------------------------------------------*/
|
||||
/* Global constant data ------------------------------------------------------*/
|
||||
@@ -208,19 +211,20 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
|
||||
// respond(response_channel, use_checksum, "Signature: %#x", STM_ID_GetSignature());
|
||||
// respond(response_channel, use_checksum, "Revision: %#x", STM_ID_GetRevision());
|
||||
// respond(response_channel, use_checksum, "Flash Size: %#x KiB", STM_ID_GetFlashSize());
|
||||
respond(response_channel, use_checksum, "Hardware version: %d.%d-%dV", HW_VERSION_MAJOR, HW_VERSION_MINOR, HW_VERSION_VOLTAGE);
|
||||
respond(response_channel, use_checksum, "Firmware version: %d.%d.%d", FW_VERSION_MAJOR, FW_VERSION_MINOR, FW_VERSION_REVISION);
|
||||
respond(response_channel, use_checksum, "Hardware version: %d.%d-%dV", odrv.hw_version_major_, odrv.hw_version_minor_, odrv.hw_version_variant_);
|
||||
respond(response_channel, use_checksum, "Firmware version: %d.%d.%d", odrv.fw_version_major_, odrv.fw_version_minor_, odrv.fw_version_revision_);
|
||||
respond(response_channel, use_checksum, "Serial number: %s", serial_number_str);
|
||||
|
||||
} else if (cmd[0] == 's'){ // System
|
||||
if(cmd[1] == 's') { // Save config
|
||||
save_configuration();
|
||||
odrv.save_configuration();
|
||||
} else if (cmd[1] == 'e'){ // Erase config
|
||||
erase_configuration();
|
||||
odrv.erase_configuration();
|
||||
} else if (cmd[1] == 'r'){ // Reboot
|
||||
NVIC_SystemReset();
|
||||
odrv.reboot();
|
||||
}
|
||||
|
||||
#if 0
|
||||
} else if (cmd[0] == 'r') { // read property
|
||||
char name[MAX_LINE_LENGTH];
|
||||
int numscan = sscanf(cmd, "r %255s", name);
|
||||
@@ -256,6 +260,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
|
||||
respond(response_channel, use_checksum, "not implemented");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} else if (cmd[0] == 'u') { // Update axis watchdog.
|
||||
unsigned motor_number;
|
||||
|
||||
@@ -188,7 +188,7 @@ void CANSimple::set_axis_nodeid_callback(Axis* axis, can_Message_t& msg) {
|
||||
}
|
||||
|
||||
void CANSimple::set_axis_requested_state_callback(Axis* axis, can_Message_t& msg) {
|
||||
axis->requested_state_ = static_cast<Axis::State_t>(can_getSignal<int32_t>(msg, 0, 16, true));
|
||||
axis->requested_state_ = static_cast<Axis::AxisState>(can_getSignal<int32_t>(msg, 0, 16, true));
|
||||
}
|
||||
void CANSimple::set_axis_startup_config_callback(Axis* axis, can_Message_t& msg) {
|
||||
// Not Implemented
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "utils.hpp"
|
||||
#include "gpio_utils.hpp"
|
||||
|
||||
#include "../build/version.h" // autogenerated based on Git state
|
||||
#include "../autogen/version.h" // autogenerated based on Git state
|
||||
|
||||
#include <cmsis_os.h>
|
||||
#include <memory>
|
||||
@@ -36,50 +36,11 @@ char serial_number_str[13]; // 12 digits + null termination
|
||||
/* Private constant data -----------------------------------------------------*/
|
||||
/* Private variables ---------------------------------------------------------*/
|
||||
|
||||
#if HW_VERSION_MAJOR == 3
|
||||
// Determine start address of the OTP struct:
|
||||
// The OTP is organized into 16-byte blocks.
|
||||
// If the first block starts with "0xfe" we use the first block.
|
||||
// If the first block starts with "0x00" and the second block starts with "0xfe",
|
||||
// we use the second block. This gives the user the chance to screw up once.
|
||||
// If none of the above is the case, we consider the OTP invalid (otp_ptr will be NULL).
|
||||
const uint8_t* otp_ptr =
|
||||
(*(uint8_t*)FLASH_OTP_BASE == 0xfe) ? (uint8_t*)FLASH_OTP_BASE :
|
||||
(*(uint8_t*)FLASH_OTP_BASE != 0x00) ? NULL :
|
||||
(*(uint8_t*)(FLASH_OTP_BASE + 0x10) != 0xfe) ? NULL :
|
||||
(uint8_t*)(FLASH_OTP_BASE + 0x10);
|
||||
|
||||
// Read hardware version from OTP if available, otherwise fall back
|
||||
// to software defined version.
|
||||
const uint8_t hw_version_major = otp_ptr ? otp_ptr[3] : HW_VERSION_MAJOR;
|
||||
const uint8_t hw_version_minor = otp_ptr ? otp_ptr[4] : HW_VERSION_MINOR;
|
||||
const uint8_t hw_version_variant = otp_ptr ? otp_ptr[5] : HW_VERSION_VOLTAGE;
|
||||
#else
|
||||
#error "not implemented"
|
||||
#endif
|
||||
|
||||
// the corresponding macros are defined in the autogenerated version.h
|
||||
const uint8_t fw_version_major = FW_VERSION_MAJOR;
|
||||
const uint8_t fw_version_minor = FW_VERSION_MINOR;
|
||||
const uint8_t fw_version_revision = FW_VERSION_REVISION;
|
||||
const uint8_t fw_version_unreleased = FW_VERSION_UNRELEASED; // 0 for official releases, 1 otherwise
|
||||
|
||||
osThreadId comm_thread;
|
||||
const uint32_t stack_size_comm_thread = 4096; // Bytes
|
||||
volatile bool endpoint_list_valid = false;
|
||||
|
||||
static uint32_t test_property = 0;
|
||||
|
||||
/* Private function prototypes -----------------------------------------------*/
|
||||
|
||||
auto make_protocol_definitions(PWMMapping_t& mapping) {
|
||||
return make_protocol_member_list(
|
||||
make_protocol_property("endpoint", &mapping.endpoint),
|
||||
make_protocol_property("min", &mapping.min),
|
||||
make_protocol_property("max", &mapping.max)
|
||||
);
|
||||
}
|
||||
|
||||
/* Function implementations --------------------------------------------------*/
|
||||
|
||||
void init_communication(void) {
|
||||
@@ -96,120 +57,6 @@ void init_communication(void) {
|
||||
float oscilloscope[OSCILLOSCOPE_SIZE] = {0};
|
||||
size_t oscilloscope_pos = 0;
|
||||
|
||||
// Helper class because the protocol library doesn't yet
|
||||
// support non-member functions
|
||||
// TODO: make this go away
|
||||
class StaticFunctions {
|
||||
public:
|
||||
void save_configuration_helper() { save_configuration(); }
|
||||
void erase_configuration_helper() { erase_configuration(); }
|
||||
void NVIC_SystemReset_helper() { NVIC_SystemReset(); }
|
||||
void enter_dfu_mode_helper() { enter_dfu_mode(); }
|
||||
float get_oscilloscope_val(uint32_t index) { return oscilloscope[index]; }
|
||||
float get_adc_voltage_(uint32_t gpio) { return get_adc_voltage(get_gpio_port_by_pin(gpio), get_gpio_pin_by_pin(gpio)); }
|
||||
int32_t test_function(int32_t delta) { static int cnt = 0; return cnt += delta; }
|
||||
} static_functions;
|
||||
|
||||
// When adding new functions/variables to the protocol, be careful not to
|
||||
// blow the communication stack. You can check comm_stack_info to see
|
||||
// how much headroom you have.
|
||||
static inline auto make_obj_tree() {
|
||||
return make_protocol_member_list(
|
||||
make_protocol_ro_property("vbus_voltage", &vbus_voltage),
|
||||
make_protocol_ro_property("ibus", &ibus_),
|
||||
make_protocol_ro_property("serial_number", &serial_number),
|
||||
make_protocol_ro_property("hw_version_major", &hw_version_major),
|
||||
make_protocol_ro_property("hw_version_minor", &hw_version_minor),
|
||||
make_protocol_ro_property("hw_version_variant", &hw_version_variant),
|
||||
make_protocol_ro_property("fw_version_major", &fw_version_major),
|
||||
make_protocol_ro_property("fw_version_minor", &fw_version_minor),
|
||||
make_protocol_ro_property("fw_version_revision", &fw_version_revision),
|
||||
make_protocol_ro_property("fw_version_unreleased", &fw_version_unreleased),
|
||||
make_protocol_ro_property("user_config_loaded", const_cast<const bool *>(&user_config_loaded_)),
|
||||
make_protocol_ro_property("brake_resistor_armed", &brake_resistor_armed),
|
||||
make_protocol_property("brake_resistor_saturated", &brake_resistor_saturated),
|
||||
make_protocol_object("system_stats",
|
||||
make_protocol_ro_property("uptime", &system_stats_.uptime),
|
||||
make_protocol_ro_property("min_heap_space", &system_stats_.min_heap_space),
|
||||
make_protocol_ro_property("min_stack_space_axis0", &system_stats_.min_stack_space_axis0),
|
||||
make_protocol_ro_property("min_stack_space_axis1", &system_stats_.min_stack_space_axis1),
|
||||
make_protocol_ro_property("min_stack_space_comms", &system_stats_.min_stack_space_comms),
|
||||
make_protocol_ro_property("min_stack_space_usb", &system_stats_.min_stack_space_usb),
|
||||
make_protocol_ro_property("min_stack_space_uart", &system_stats_.min_stack_space_uart),
|
||||
make_protocol_ro_property("min_stack_space_can", &system_stats_.min_stack_space_can),
|
||||
make_protocol_ro_property("min_stack_space_usb_irq", &system_stats_.min_stack_space_usb_irq),
|
||||
make_protocol_ro_property("min_stack_space_startup", &system_stats_.min_stack_space_startup),
|
||||
make_protocol_ro_property("stack_usage_axis0", &system_stats_.stack_usage_axis0),
|
||||
make_protocol_ro_property("stack_usage_axis1", &system_stats_.stack_usage_axis1),
|
||||
make_protocol_ro_property("stack_usage_comms", &system_stats_.stack_usage_comms),
|
||||
make_protocol_ro_property("stack_usage_usb", &system_stats_.stack_usage_usb),
|
||||
make_protocol_ro_property("stack_usage_uart", &system_stats_.stack_usage_uart),
|
||||
make_protocol_ro_property("stack_usage_usb_irq", &system_stats_.stack_usage_usb_irq),
|
||||
make_protocol_ro_property("stack_usage_startup", &system_stats_.stack_usage_startup),
|
||||
make_protocol_ro_property("stack_usage_can", &system_stats_.stack_usage_can),
|
||||
make_protocol_object("usb",
|
||||
make_protocol_ro_property("rx_cnt", &usb_stats_.rx_cnt),
|
||||
make_protocol_ro_property("tx_cnt", &usb_stats_.tx_cnt),
|
||||
make_protocol_ro_property("tx_overrun_cnt", &usb_stats_.tx_overrun_cnt)
|
||||
),
|
||||
make_protocol_object("i2c",
|
||||
make_protocol_ro_property("addr", &i2c_stats_.addr),
|
||||
make_protocol_ro_property("addr_match_cnt", &i2c_stats_.addr_match_cnt),
|
||||
make_protocol_ro_property("rx_cnt", &i2c_stats_.rx_cnt),
|
||||
make_protocol_ro_property("error_cnt", &i2c_stats_.error_cnt)
|
||||
)
|
||||
),
|
||||
make_protocol_object("config",
|
||||
make_protocol_property("brake_resistance", &board_config.brake_resistance),
|
||||
make_protocol_property("max_regen_current", &board_config.max_regen_current),
|
||||
// TODO: changing this currently requires a reboot - fix this
|
||||
make_protocol_property("enable_uart", &board_config.enable_uart),
|
||||
make_protocol_property("uart_baudrate", &board_config.uart_baudrate), // requires a reboot
|
||||
make_protocol_property("enable_i2c_instead_of_can" , &board_config.enable_i2c_instead_of_can), // requires a reboot
|
||||
make_protocol_property("enable_ascii_protocol_on_usb", &board_config.enable_ascii_protocol_on_usb),
|
||||
make_protocol_property("dc_bus_undervoltage_trip_level", &board_config.dc_bus_undervoltage_trip_level),
|
||||
make_protocol_property("dc_bus_overvoltage_trip_level", &board_config.dc_bus_overvoltage_trip_level),
|
||||
make_protocol_property("enable_dc_bus_overvoltage_ramp", &board_config.enable_dc_bus_overvoltage_ramp),
|
||||
make_protocol_property("dc_bus_overvoltage_ramp_start", &board_config.dc_bus_overvoltage_ramp_start),
|
||||
make_protocol_property("dc_bus_overvoltage_ramp_end", &board_config.dc_bus_overvoltage_ramp_end),
|
||||
make_protocol_property("dc_max_negative_current", &board_config.dc_max_negative_current),
|
||||
make_protocol_property("dc_max_positive_current", &board_config.dc_max_positive_current),
|
||||
#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3
|
||||
make_protocol_object("gpio1_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[0])),
|
||||
make_protocol_object("gpio2_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[1])),
|
||||
make_protocol_object("gpio3_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[2])),
|
||||
#endif
|
||||
make_protocol_object("gpio4_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[3])),
|
||||
|
||||
make_protocol_object("gpio3_analog_mapping", make_protocol_definitions(board_config.analog_mappings[2])),
|
||||
make_protocol_object("gpio4_analog_mapping", make_protocol_definitions(board_config.analog_mappings[3]))
|
||||
),
|
||||
make_protocol_object("axis0", axes[0]->make_protocol_definitions()),
|
||||
make_protocol_object("axis1", axes[1]->make_protocol_definitions()),
|
||||
make_protocol_object("can", odCAN->make_protocol_definitions()),
|
||||
make_protocol_property("test_property", &test_property),
|
||||
make_protocol_function("test_function", static_functions, &StaticFunctions::test_function, "delta"),
|
||||
make_protocol_function("get_oscilloscope_val", static_functions, &StaticFunctions::get_oscilloscope_val, "index"),
|
||||
make_protocol_function("get_adc_voltage", static_functions, &StaticFunctions::get_adc_voltage_, "gpio"),
|
||||
make_protocol_function("save_configuration", static_functions, &StaticFunctions::save_configuration_helper),
|
||||
make_protocol_function("erase_configuration", static_functions, &StaticFunctions::erase_configuration_helper),
|
||||
make_protocol_function("reboot", static_functions, &StaticFunctions::NVIC_SystemReset_helper),
|
||||
make_protocol_function("enter_dfu_mode", static_functions, &StaticFunctions::enter_dfu_mode_helper)
|
||||
);
|
||||
}
|
||||
|
||||
using tree_type = decltype(make_obj_tree());
|
||||
uint8_t tree_buffer[sizeof(tree_type)];
|
||||
|
||||
|
||||
void initTree(){
|
||||
// TODO: this is supposed to use the move constructor, but currently
|
||||
// the compiler uses the copy-constructor instead. Thus the make_obj_tree
|
||||
// ends up with a stupid stack size of around 8000 bytes. Fix this.
|
||||
auto tree_ptr = new (tree_buffer) tree_type(make_obj_tree());
|
||||
fibre_publish(*tree_ptr);
|
||||
}
|
||||
|
||||
// Thread to handle deffered processing of USB interrupt, and
|
||||
// read commands out of the UART DMA circular buffer
|
||||
void communication_task(void * ctx) {
|
||||
@@ -220,7 +67,7 @@ void communication_task(void * ctx) {
|
||||
|
||||
start_uart_server();
|
||||
start_usb_server();
|
||||
if (board_config.enable_i2c_instead_of_can) {
|
||||
if (odrv.config_.enable_i2c_instead_of_can) {
|
||||
start_i2c_server();
|
||||
} else {
|
||||
odCAN->start_can_server();
|
||||
@@ -245,3 +92,7 @@ int _write(int file, const char* data, int len) {
|
||||
#endif
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
#include "../autogen/function_stubs.hpp"
|
||||
#include "../autogen/endpoints.hpp"
|
||||
|
||||
@@ -17,10 +17,6 @@ extern "C" {
|
||||
extern osThreadId comm_thread;
|
||||
extern const uint32_t stack_size_comm_thread;
|
||||
|
||||
extern const uint8_t hw_version_major;
|
||||
extern const uint8_t hw_version_minor;
|
||||
extern const uint8_t hw_version_variant;
|
||||
|
||||
void init_communication(void);
|
||||
void initTree();
|
||||
void communication_task(void * ctx);
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
// std::unordered_map<CAN_HandleTypeDef *, ODriveCAN *> ctxMap;
|
||||
|
||||
// Constructor is called by communication.cpp and the handle is assigned appropriately
|
||||
ODriveCAN::ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config)
|
||||
: handle_{handle},
|
||||
config_{config} {
|
||||
ODriveCAN::ODriveCAN(ODriveCAN::Config_t &config, CAN_HandleTypeDef *handle)
|
||||
: config_{config},
|
||||
handle_{handle} {
|
||||
// ctxMap[handle_] = this;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ void ODriveCAN::can_server_thread() {
|
||||
while (available()) {
|
||||
read(rxmsg);
|
||||
switch (config_.protocol) {
|
||||
case CAN_PROTOCOL_SIMPLE:
|
||||
case Config_t::PROTOCOL_SIMPLE:
|
||||
CANSimple::handle_can_message(rxmsg);
|
||||
break;
|
||||
}
|
||||
@@ -183,7 +183,7 @@ void ODriveCAN::send_heartbeat(Axis *axis) {
|
||||
uint32_t now = osKernelSysTick();
|
||||
if ((now - axis->last_heartbeat_) >= axis->config_.can_heartbeat_rate_ms) {
|
||||
switch (config_.protocol) {
|
||||
case CAN_PROTOCOL_SIMPLE:
|
||||
case Config_t::PROTOCOL_SIMPLE:
|
||||
CANSimple::send_heartbeat(axis);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -19,23 +19,14 @@ enum {
|
||||
CAN_BAUD_1M = 1000000
|
||||
};
|
||||
|
||||
enum CAN_Protocol_t {
|
||||
CAN_PROTOCOL_SIMPLE
|
||||
};
|
||||
|
||||
class ODriveCAN {
|
||||
class ODriveCAN : public OdriveIntf::CanIntf {
|
||||
public:
|
||||
struct Config_t {
|
||||
struct Config_t : ConfigIntf {
|
||||
uint32_t baud_rate = CAN_BAUD_250K;
|
||||
CAN_Protocol_t protocol = CAN_PROTOCOL_SIMPLE;
|
||||
Protocol protocol = PROTOCOL_SIMPLE;
|
||||
};
|
||||
|
||||
enum Error {
|
||||
ERROR_NONE = 0x00,
|
||||
ERROR_DUPLICATE_CAN_IDS = 0x01
|
||||
};
|
||||
|
||||
ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config);
|
||||
ODriveCAN(ODriveCAN::Config_t &config, CAN_HandleTypeDef *handle);
|
||||
|
||||
// Thread Relevant Data
|
||||
osThreadId thread_id_;
|
||||
@@ -55,24 +46,12 @@ class ODriveCAN {
|
||||
uint32_t write(can_Message_t &txmsg);
|
||||
bool read(can_Message_t &rxmsg);
|
||||
|
||||
// Communication Protocol Handling
|
||||
auto make_protocol_definitions() {
|
||||
return make_protocol_member_list(
|
||||
make_protocol_property("error", &error_),
|
||||
make_protocol_object("config",
|
||||
make_protocol_ro_property("baud_rate", &config_.baud_rate)),
|
||||
make_protocol_property("can_protocol", &config_.protocol),
|
||||
make_protocol_function("set_baud_rate", *this, &ODriveCAN::set_baud_rate, "baudRate"));
|
||||
}
|
||||
|
||||
private:
|
||||
CAN_HandleTypeDef *handle_ = nullptr;
|
||||
ODriveCAN::Config_t &config_;
|
||||
|
||||
private:
|
||||
CAN_HandleTypeDef *handle_ = nullptr;
|
||||
|
||||
void set_baud_rate(uint32_t baudRate);
|
||||
};
|
||||
|
||||
|
||||
DEFINE_ENUM_FLAG_OPERATORS(ODriveCAN::Error)
|
||||
|
||||
#endif // __INTERFACE_CAN_HPP
|
||||
|
||||
@@ -127,7 +127,7 @@ static void usb_server_thread(void * ctx) {
|
||||
// CDC Interface
|
||||
if (CDC_interface.data_pending) {
|
||||
CDC_interface.data_pending = false;
|
||||
if (board_config.enable_ascii_protocol_on_usb) {
|
||||
if (odrv.config_.enable_ascii_protocol_on_usb) {
|
||||
ASCII_protocol_parse_stream(CDC_interface.rx_buf,
|
||||
CDC_interface.rx_len, usb_stream_output);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*[# This is the original template, thus the warning below does not apply to this file #]
|
||||
* ============================ WARNING ============================
|
||||
* ==== This is an autogenerated file. ====
|
||||
* ==== Any changes to this file will be lost when recompiling. ====
|
||||
* =================================================================
|
||||
*
|
||||
* This file contains the toplevel handler for Fibre v0.1 endpoint operations.
|
||||
*
|
||||
* This endpoint-oriented approach will be deprecated in Fibre v0.2 in favor of
|
||||
* a function-oriented approach and a more powerful object model.
|
||||
*
|
||||
*/
|
||||
#ifndef __FIBRE_INTERFACES_HPP
|
||||
#define __FIBRE_INTERFACES_HPP
|
||||
|
||||
namespace fibre {
|
||||
|
||||
const unsigned char embedded_json[] = [[embedded_endpoint_definitions | to_c_string]];
|
||||
const size_t embedded_json_length = sizeof(embedded_json) - 1;
|
||||
const uint16_t json_crc_ = calc_crc16<CANONICAL_CRC16_POLYNOMIAL>(PROTOCOL_VERSION, embedded_json, embedded_json_length);
|
||||
const uint32_t json_version_id_ = (json_crc_ << 16) | calc_crc16<CANONICAL_CRC16_POLYNOMIAL>(json_crc_, embedded_json, embedded_json_length);
|
||||
|
||||
// Note: with -Og this function reserves a huge amount of stack space because it
|
||||
// reserves separate space for the stack frame of each of the inlined functions.
|
||||
// The minimum known set of flags to prevent this is `-O1 -fipa-sra`.
|
||||
// `-O2` is a superset of this so that's what we use here.
|
||||
bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) __attribute__ ((optimize(2)));
|
||||
|
||||
bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) {
|
||||
switch (idx) {
|
||||
[%- for endpoint in endpoints %]
|
||||
case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_type]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_type]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break;
|
||||
[%- endfor %]
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_endpoint_ref_valid(endpoint_ref_t endpoint_ref) {
|
||||
if (endpoint_ref.json_crc != json_crc_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (endpoint_ref.endpoint_id) {
|
||||
[%- for endpoint in endpoints %]
|
||||
case [[endpoint.id]]: return true;
|
||||
[%- endfor %]
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool set_endpoint_from_float(endpoint_ref_t endpoint_ref, float value) {
|
||||
if (endpoint_ref.json_crc != json_crc_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
// TODO: implement
|
||||
/*cbufptr_t input_buffer{};
|
||||
bufptr_t output_buffer{};
|
||||
|
||||
switch (idx) {
|
||||
[%- for endpoint in endpoints %]
|
||||
case [[endpoint.id]]: return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_type]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %]&input_buffer, &output_buffer);
|
||||
[%- endfor %]
|
||||
default: return false;
|
||||
}*/
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // __FIBRE_INTERFACES_HPP
|
||||
@@ -0,0 +1,40 @@
|
||||
/*[# This is the original template, thus the warning below does not apply to this file #]
|
||||
* ============================ WARNING ============================
|
||||
* ==== This is an autogenerated file. ====
|
||||
* ==== Any changes to this file will be lost when recompiling. ====
|
||||
* =================================================================
|
||||
*
|
||||
* This file contains serializing/deserializing stubs for the functions defined
|
||||
* in your interface file.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <fibre/bufptr.hpp>
|
||||
|
||||
[% for intf in interfaces.values() %]
|
||||
[% for func in intf.functions.values() %]
|
||||
static inline bool [[func.fullname | to_snake_case]]([% for arg in func.in.values() %]std::optional<[[arg.type.c_type]]> in_[[arg.name]], [% endfor %][% for arg in func.out.values() %][[arg.type.c_type]]* out_[[arg.name]], [% endfor %]fibre::cbufptr_t* input_buffer, fibre::bufptr_t* output_buffer) {
|
||||
[%- if func.in %]
|
||||
bool success = [% for arg in func.in.values() %](in_[[arg.name]].has_value() || (in_[[arg.name]] = fibre::Codec<[[arg.type.c_type]]>::decode(input_buffer)).has_value()[% if arg.optional %] || true[% endif %])[% if not loop.last %]
|
||||
&& [% endif %][% endfor %];
|
||||
[%- else %]
|
||||
bool success = true;
|
||||
[%- endif %]
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
[%- if func.implementation %]
|
||||
[% if func.out %]std::tuple<[% for arg in func.out.values() %][[arg.type.c_type]][[', ' if not loop.last]][% endfor %]> ret = [% endif %][[func.implementation]]([% for arg in func.in.values() %]in_[[arg.name]][% if not arg.optional %].value()[% endif %][[', ' if not loop.last]][% endfor %]);
|
||||
[%- else %]
|
||||
[% if func.out %]std::tuple<[% for arg in func.out.values() %][[arg.type.c_type]][[', ' if not loop.last]][% endfor %]> ret = [% endif %]in_[[(func.in.values() | first).name]].value()->[[func.name]]([% for arg in func.in.values() | skip_first %]in_[[arg.name]][% if not arg.optional %].value()[% endif %][[', ' if not loop.last]][% endfor %]);
|
||||
[%- endif %]
|
||||
[%- if func.out %]
|
||||
return [% for arg in func.out.values() %]((out_[[arg.name]] && ((*out_[[arg.name]] = std::get<[[loop.index0]]>(ret)), true)) || fibre::Codec<[[arg.type.c_type]]>::encode(std::get<[[loop.index0]]>(ret), output_buffer))[% if not loop.last %]
|
||||
&& [% endif %][% endfor %];
|
||||
[%- else %]
|
||||
return true;
|
||||
[%- endif %]
|
||||
}
|
||||
[% endfor %]
|
||||
[% endfor %]
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#ifndef __FIBRE_BUFPTR_HPP
|
||||
#define __FIBRE_BUFPTR_HPP
|
||||
|
||||
namespace fibre {
|
||||
|
||||
static inline bool soft_assert(bool expr) { return expr; } // TODO: implement
|
||||
|
||||
/**
|
||||
* @brief Holds a reference to a buffer and a length.
|
||||
* Since this class implements begin() and end(), you can use it with many
|
||||
* standard algorithms that operate on iterable objects.
|
||||
*/
|
||||
template<typename T>
|
||||
struct generic_bufptr_t {
|
||||
using iterator = T*;
|
||||
using const_iterator = const T*;
|
||||
|
||||
generic_bufptr_t(T* begin, size_t length) : begin_(begin), end_(begin + length) {}
|
||||
|
||||
generic_bufptr_t(T* begin, T* end) : begin_(begin), end_(end) {}
|
||||
|
||||
generic_bufptr_t() : begin_(nullptr), end_(nullptr) {}
|
||||
|
||||
template<size_t I>
|
||||
generic_bufptr_t(T (&begin)[I]) : generic_bufptr_t(begin, I) {}
|
||||
|
||||
generic_bufptr_t(const std::vector<std::remove_const_t<T>>& vector)
|
||||
: generic_bufptr_t(vector.data(), vector.size()) {}
|
||||
|
||||
generic_bufptr_t(const generic_bufptr_t<std::remove_const_t<T>>& other)
|
||||
: generic_bufptr_t(other.begin_, other.end_) {}
|
||||
|
||||
generic_bufptr_t& operator+=(size_t num) {
|
||||
if (!soft_assert(num <= size())) {
|
||||
num = size();
|
||||
}
|
||||
begin_ += num;
|
||||
return *this;
|
||||
}
|
||||
|
||||
generic_bufptr_t operator++(int) {
|
||||
generic_bufptr_t result = *this;
|
||||
*this += 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
T& operator*() {
|
||||
return *begin_;
|
||||
}
|
||||
|
||||
generic_bufptr_t take(size_t num) const {
|
||||
if (!soft_assert(num <= size())) {
|
||||
num = size();
|
||||
}
|
||||
generic_bufptr_t result = {begin_, num};
|
||||
return result;
|
||||
}
|
||||
|
||||
generic_bufptr_t skip(size_t num, size_t* processed_bytes = nullptr) const {
|
||||
if (!soft_assert(num <= size())) {
|
||||
num = size();
|
||||
}
|
||||
if (processed_bytes)
|
||||
(*processed_bytes) += num;
|
||||
return {begin_ + num, end_};
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return end_ - begin_;
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
T*& begin() { return begin_; }
|
||||
T*& end() { return end_; }
|
||||
T* const & begin() const { return begin_; }
|
||||
T* const & end() const { return end_; }
|
||||
T& front() const { return *begin(); }
|
||||
T& back() const { return *(end() - 1); }
|
||||
T& operator[](size_t idx) { return *(begin() + idx); }
|
||||
|
||||
T* begin_;
|
||||
T* end_;
|
||||
};
|
||||
|
||||
using cbufptr_t = generic_bufptr_t<const unsigned char>;
|
||||
using bufptr_t = generic_bufptr_t<unsigned char>;
|
||||
|
||||
}
|
||||
|
||||
#endif // __FIBRE_BUFPTR_HPP
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
||||
#ifndef __FIBRE_SIMPLE_SERDES
|
||||
#define __FIBRE_SIMPLE_SERDES
|
||||
|
||||
//#include "stream.hpp"
|
||||
|
||||
|
||||
template<typename T, bool BigEndian, typename = void>
|
||||
struct SimpleSerializer;
|
||||
template<typename T>
|
||||
using LittleEndianSerializer = SimpleSerializer<T, false>;
|
||||
template<typename T>
|
||||
using BigEndianSerializer = SimpleSerializer<T, true>;
|
||||
|
||||
|
||||
/* @brief Serializer/deserializer for arbitrary integral number types */
|
||||
// TODO: allow reading an arbitrary number of bits
|
||||
template<typename T, bool BigEndian>
|
||||
struct SimpleSerializer<T, BigEndian, typename std::enable_if_t<std::is_integral<T>::value>> {
|
||||
static constexpr size_t BIT_WIDTH = std::numeric_limits<T>::digits;
|
||||
static constexpr size_t BYTE_WIDTH = (BIT_WIDTH + 7) / 8;
|
||||
|
||||
template<typename TIterator>
|
||||
static std::optional<T> read(TIterator* begin, TIterator end = nullptr) {
|
||||
T result = 0;
|
||||
if (BigEndian) {
|
||||
for (size_t i = BYTE_WIDTH; i > 0; (i++, (*begin)++)) {
|
||||
if (end && !(*begin < end))
|
||||
return std::nullopt;
|
||||
uint8_t byte = **begin;
|
||||
result |= static_cast<T>(byte) << ((i - 1) << 3);
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < BYTE_WIDTH; (i++, (*begin)++)) {
|
||||
if (end && !(*begin < end))
|
||||
return std::nullopt;
|
||||
uint8_t byte = **begin;
|
||||
result |= static_cast<T>(byte) << (i << 3);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename TIterator>
|
||||
static bool write(T value, TIterator* begin, TIterator end = nullptr) {
|
||||
if (BigEndian) {
|
||||
for (size_t i = BYTE_WIDTH; i > 0; (i--, (*begin)++)) {
|
||||
if (end && !(*begin < end))
|
||||
return false;
|
||||
uint8_t byte = static_cast<uint8_t>((value >> ((i - 1) << 3)) & 0xff);
|
||||
**begin = byte;
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < BYTE_WIDTH; (i++, (*begin)++)) {
|
||||
if (end && !(*begin < end))
|
||||
return false;
|
||||
uint8_t byte = static_cast<uint8_t>((value >> (i << 3)) & 0xff);
|
||||
**begin = byte;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
inline std::optional<T> read_le(fibre::cbufptr_t* buffer) {
|
||||
static_assert(is_complete<LittleEndianSerializer<T>>(), "no LittleEndianSerializer is defined for type T");
|
||||
return LittleEndianSerializer<T>::read(&buffer->begin(), buffer->end());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool write_le(T value, fibre::bufptr_t* buffer) {
|
||||
static_assert(is_complete<LittleEndianSerializer<T>>(), "no LittleEndianSerializer is defined for type T");
|
||||
return LittleEndianSerializer<T>::write(value, &buffer->begin(), buffer->end());
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,67 @@
|
||||
/*[# This is the original template, thus the warning below does not apply to this file #]
|
||||
* ============================ WARNING ============================
|
||||
* ==== This is an autogenerated file. ====
|
||||
* ==== Any changes to this file will be lost when recompiling. ====
|
||||
* =================================================================
|
||||
*
|
||||
* This file contains base classes that correspond to the interfaces defined in
|
||||
* your interface file. The objects you publish should inherit from these
|
||||
* interfaces.
|
||||
*
|
||||
*/
|
||||
|
||||
[%- macro rettype(func) %]
|
||||
[%- if not func.out -%]
|
||||
void
|
||||
[%- elif func.out | length == 1 -%]
|
||||
[[(func.out.values() | first).type.c_type]]
|
||||
[%- else -%]
|
||||
[% for arg in func.out.values() %][[arg.type]][[', ' if not loop.last]][% endfor %]
|
||||
[%- endif -%]
|
||||
[%- endmacro %]
|
||||
|
||||
[%- macro render_interface(intf) %]
|
||||
class [[intf.name | to_pascal_case]]Intf {
|
||||
public:
|
||||
[%- for intf in intf.interfaces -%]
|
||||
[[render_interface(intf) | indent(4)]]
|
||||
[%- endfor %]
|
||||
[%- for enum in intf.enums %]
|
||||
enum [[enum.name | to_pascal_case]] {
|
||||
[%- for k, value in enum['values'].items() %]
|
||||
[[((enum.name + k) | to_macro_case).ljust(32)]] = [% if enum.is_flags %]0x[['%08x' | format(value.value)]][% else %][[value.value]][% endif %],
|
||||
[%- endfor %]
|
||||
};
|
||||
[%- endfor %]
|
||||
[%- for func in intf.functions.values() %]
|
||||
virtual [[rettype(func)]] [[func.name | to_snake_case]]([% for in in func.in.values() %][% if loop.index0 %][[in.type.c_type]] [[in.name]][[', ' if not loop.last]][% endif %][% endfor %]) = 0;
|
||||
[%- endfor %]
|
||||
[%- for func in intf.functions.values() %]
|
||||
[%- for k, arg in func.in.items() | skip_first %]
|
||||
[[arg.type.c_type]] [[func.name | to_snake_case]]_in_[[arg.name]]_; // for internal use by Fibre
|
||||
[%- endfor %]
|
||||
[%- for k, arg in func.out.items() %]
|
||||
[[arg.type.c_type]] [[func.name | to_snake_case]]_out_[[arg.name]]_; // for internal use by Fibre
|
||||
[%- endfor %]
|
||||
[%- endfor %]
|
||||
};
|
||||
[%- endmacro %]
|
||||
|
||||
[% for intf in toplevel_interfaces %]
|
||||
[[render_interface(intf)]]
|
||||
[% endfor %]
|
||||
|
||||
[%- for _, enum in value_types.items() %]
|
||||
[%- if enum.is_flags %]
|
||||
// this is technically not thread-safe but practically it might be
|
||||
inline [[enum.c_type]] operator | ([[enum.c_type]] a, [[enum.c_type]] b) { return static_cast<[[enum.c_type]]>(static_cast<std::underlying_type_t<[[enum.c_type]]>>(a) | static_cast<std::underlying_type_t<[[enum.c_type]]>>(b)); }
|
||||
inline [[enum.c_type]] operator & ([[enum.c_type]] a, [[enum.c_type]] b) { return static_cast<[[enum.c_type]]>(static_cast<std::underlying_type_t<[[enum.c_type]]>>(a) & static_cast<std::underlying_type_t<[[enum.c_type]]>>(b)); }
|
||||
inline [[enum.c_type]] operator ^ ([[enum.c_type]] a, [[enum.c_type]] b) { return static_cast<[[enum.c_type]]>(static_cast<std::underlying_type_t<[[enum.c_type]]>>(a) ^ static_cast<std::underlying_type_t<[[enum.c_type]]>>(b)); }
|
||||
inline [[enum.c_type]]& operator |= ([[enum.c_type]] &a, [[enum.c_type]] b) { return reinterpret_cast<[[enum.c_type]]&>(reinterpret_cast<std::underlying_type_t<[[enum.c_type]]>&>(a) |= static_cast<std::underlying_type_t<[[enum.c_type]]>>(b)); }
|
||||
inline [[enum.c_type]]& operator &= ([[enum.c_type]] &a, [[enum.c_type]] b) { return reinterpret_cast<[[enum.c_type]]&>(reinterpret_cast<std::underlying_type_t<[[enum.c_type]]>&>(a) &= static_cast<std::underlying_type_t<[[enum.c_type]]>>(b)); }
|
||||
inline [[enum.c_type]]& operator ^= ([[enum.c_type]] &a, [[enum.c_type]] b) { return reinterpret_cast<[[enum.c_type]]&>(reinterpret_cast<std::underlying_type_t<[[enum.c_type]]>&>(a) ^= static_cast<std::underlying_type_t<[[enum.c_type]]>>(b)); }
|
||||
inline [[enum.c_type]] operator ~ ([[enum.c_type]] a) { return static_cast<[[enum.c_type]]>(~static_cast<std::underlying_type_t<[[enum.c_type]]>>(a)); }
|
||||
[%- endif %]
|
||||
[%- endfor %]
|
||||
|
||||
|
||||
@@ -13,19 +13,11 @@
|
||||
/* Global constant data ------------------------------------------------------*/
|
||||
/* Global variables ----------------------------------------------------------*/
|
||||
|
||||
Endpoint** endpoint_list_ = nullptr; // initialized by calling fibre_publish
|
||||
size_t n_endpoints_ = 0; // initialized by calling fibre_publish
|
||||
uint16_t json_crc_; // initialized by calling fibre_publish
|
||||
uint32_t json_version_id_; // initialized by calling fibre_publish
|
||||
JSONDescriptorEndpoint json_file_endpoint_ = JSONDescriptorEndpoint();
|
||||
EndpointProvider* application_endpoints_;
|
||||
|
||||
/* Private constant data -----------------------------------------------------*/
|
||||
/* Private variables ---------------------------------------------------------*/
|
||||
/* Private function prototypes -----------------------------------------------*/
|
||||
|
||||
static void hexdump(const uint8_t* buf, size_t len);
|
||||
static inline int write_string(const char* str, StreamSink* output);
|
||||
|
||||
/* Function implementations --------------------------------------------------*/
|
||||
|
||||
@@ -116,45 +108,26 @@ int StreamBasedPacketSink::process_packet(const uint8_t *buffer, size_t length)
|
||||
}
|
||||
|
||||
|
||||
|
||||
void JSONDescriptorEndpoint::write_json(size_t id, StreamSink* output) {
|
||||
write_string("{\"name\":\"\",", output);
|
||||
|
||||
// write endpoint ID
|
||||
write_string("\"id\":", output);
|
||||
char id_buf[10];
|
||||
snprintf(id_buf, sizeof(id_buf), "%u", (unsigned)id); // TODO: get rid of printf
|
||||
write_string(id_buf, output);
|
||||
|
||||
write_string(",\"type\":\"json\",\"access\":\"r\"}", output);
|
||||
}
|
||||
|
||||
void JSONDescriptorEndpoint::register_endpoints(Endpoint** list, size_t id, size_t length) {
|
||||
if (id < length)
|
||||
list[id] = this;
|
||||
}
|
||||
|
||||
// Returns part of the JSON interface definition.
|
||||
void JSONDescriptorEndpoint::handle(const uint8_t* input, size_t input_length, StreamSink* output) {
|
||||
bool fibre::endpoint0_handler(fibre::cbufptr_t* input_buffer, fibre::bufptr_t* output_buffer) {
|
||||
// The request must contain a 32 bit integer to specify an offset
|
||||
if (input_length < 4)
|
||||
return;
|
||||
uint32_t offset = 0;
|
||||
read_le<uint32_t>(&offset, input);
|
||||
|
||||
// If the offset is special value 0xFFFFFFFF, send back the JSON version ID instead
|
||||
if (offset == 0xffffffff) {
|
||||
default_readwrite_endpoint_handler(&json_version_id_, nullptr, 0, output);
|
||||
std::optional<uint32_t> offset = read_le<uint32_t>(input_buffer);
|
||||
|
||||
if (!offset.has_value()) {
|
||||
// Didn't receive any offset
|
||||
return false;
|
||||
} else if (offset.value() == 0xffffffff) {
|
||||
// If the offset is special value 0xFFFFFFFF, send back the JSON version ID instead
|
||||
return write_le<uint32_t>(json_version_id_, output_buffer);
|
||||
} else if (offset.value() >= embedded_json_length) {
|
||||
// Attempt to read beyond the buffer end - return empty response
|
||||
return true;
|
||||
} else {
|
||||
NullStreamSink output_with_offset = NullStreamSink(offset, *output);
|
||||
|
||||
size_t id = 0;
|
||||
write_string("[", &output_with_offset);
|
||||
json_file_endpoint_.write_json(id, &output_with_offset);
|
||||
id += decltype(json_file_endpoint_)::endpoint_count;
|
||||
write_string(",", &output_with_offset);
|
||||
application_endpoints_->write_json(id, &output_with_offset);
|
||||
write_string("]", &output_with_offset);
|
||||
// Return part of the json file
|
||||
size_t n_copy = std::min(output_buffer->size(), embedded_json_length - (size_t)offset.value());
|
||||
memcpy(output_buffer->begin(), embedded_json + offset.value(), n_copy);
|
||||
*output_buffer = output_buffer->skip(n_copy);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,19 +149,10 @@ int BidirectionalPacketBasedChannel::process_packet(const uint8_t* buffer, size_
|
||||
bool expect_response = endpoint_id & 0x8000;
|
||||
endpoint_id &= 0x7fff;
|
||||
|
||||
if (endpoint_id >= n_endpoints_)
|
||||
return -1;
|
||||
|
||||
Endpoint* endpoint = endpoint_list_[endpoint_id];
|
||||
if (!endpoint) {
|
||||
LOG_FIBRE("critical: no endpoint at %d", endpoint_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Verify packet trailer. The expected trailer value depends on the selected endpoint.
|
||||
// For endpoint 0 this is just the protocol version, for all other endpoints it's a
|
||||
// CRC over the entire JSON descriptor tree (this may change in future versions).
|
||||
uint16_t expected_trailer = endpoint_id ? json_crc_ : PROTOCOL_VERSION;
|
||||
uint16_t expected_trailer = endpoint_id ? fibre::json_crc_ : PROTOCOL_VERSION;
|
||||
uint16_t actual_trailer = buffer[length - 2] | (buffer[length - 1] << 8);
|
||||
if (expected_trailer != actual_trailer) {
|
||||
LOG_FIBRE("trailer mismatch for endpoint %d: expected %04x, got %04x\r\n", endpoint_id, expected_trailer, actual_trailer);
|
||||
@@ -204,12 +168,13 @@ int BidirectionalPacketBasedChannel::process_packet(const uint8_t* buffer, size_
|
||||
if (expected_response_length > sizeof(tx_buf_) - 2)
|
||||
expected_response_length = sizeof(tx_buf_) - 2;
|
||||
|
||||
MemoryStreamSink output(tx_buf_ + 2, expected_response_length);
|
||||
endpoint->handle(buffer, length - 2, &output);
|
||||
fibre::cbufptr_t input_buffer{buffer, length - 2};
|
||||
fibre::bufptr_t output_buffer{tx_buf_ + 2, expected_response_length};
|
||||
fibre::endpoint_handler(endpoint_id, &input_buffer, &output_buffer);
|
||||
|
||||
// Send response
|
||||
if (expect_response) {
|
||||
size_t actual_response_length = expected_response_length - output.get_free_space() + 2;
|
||||
size_t actual_response_length = expected_response_length - output_buffer.size() + 2;
|
||||
write_le<uint16_t>(seq_no | 0x8000, tx_buf_);
|
||||
|
||||
LOG_FIBRE("send packet:\r\n");
|
||||
@@ -220,15 +185,3 @@ int BidirectionalPacketBasedChannel::process_packet(const uint8_t* buffer, size_
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool is_endpoint_ref_valid(endpoint_ref_t endpoint_ref) {
|
||||
return (endpoint_ref.json_crc == json_crc_)
|
||||
&& (endpoint_ref.endpoint_id < n_endpoints_);
|
||||
}
|
||||
|
||||
Endpoint* get_endpoint(endpoint_ref_t endpoint_ref) {
|
||||
if (is_endpoint_ref_valid(endpoint_ref))
|
||||
return endpoint_list_[endpoint_ref.endpoint_id];
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,133 @@
|
||||
# Interface Definition File
|
||||
|
||||
This document describes the rules on which the ODrive Interface Definition file is built. It is intended for ODrive contributors who wish to modify it or ODrive users who want to autogenerate their own code from this file to interface with the ODrive.
|
||||
|
||||
## Terms and Concepts
|
||||
|
||||
*Value types* are a way of saying how values of this type are serialized/deserialized to/from raw bytes.
|
||||
Value types can be:
|
||||
- one of the well-known types `bool`, `int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `int32`, `uint32`, `int64`, `uint64`, `float32`, `float64`, `fibre.Ref<Interface>`
|
||||
- An enumeration (that is, a mapping between serialized numbers and well known value names)
|
||||
- A set of flags (in many programming languages this is the same as normal enums)
|
||||
|
||||
An *interface* is a collection of features (attributes and functions) that can be implemented by an object or used by a client as a filter for object discovery.
|
||||
|
||||
A *function* is something that takes zero or more inputs from the client, does something, and then returns zero or more outputs to the client. Since these input and output arguments are transmitted as raw bytes, they each have a value type.
|
||||
|
||||
An *attribute* is a reference to a subobject which again implements some interface.
|
||||
|
||||
Many languages don't make clear distinctions between interfaces and value types so let's be clear on this: attributes _always_ have an interface type and function input/output arguments _always_ have a value type. If you see something that looks like an attribute with a value type (let's say `uint32`), it's actually an attribute with the interface type `fibre.Property<uint32>`. If you see a function argument that looks like an interface type (let's say `MyIntf`) it's actually of the value type `fibre.Ref<MyIntf>`.
|
||||
|
||||
|
||||
## File Structure
|
||||
|
||||
The top level contains a dictionary of interfaces and a dictionary of value types.
|
||||
Interfaces as well as value types can be subordinate to other interfaces. Nested names are specified using dots in between the subnames.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
interfaces:
|
||||
MyFirstInterface: ...
|
||||
MyFirstInterface.SubInterface: ...
|
||||
|
||||
valuetypes:
|
||||
MyFirstEnum: ...
|
||||
MyFirstInterface.SubEnum: ...
|
||||
```
|
||||
|
||||
## Interfaces
|
||||
|
||||
Interfaces consist of an `attributes` dictionary and a `functions` dictionary.
|
||||
|
||||
**Attributes** have a type which is either given by name as a string or directly in place.
|
||||
Even though attributes conceptually and internally are always resolved to an interface type, for your convenience you can also give a value type which is then implicitly resolved to `fibre.Property<value type>`.
|
||||
|
||||
If the type is given as a string, it is resolved based on the scope in which it occurs. The search precedence is as follows: The innermost scope is searched first for an interface with that name and then for a value type with that name. If both names don't exist, the next outer scope is checked. Note that the order in which types are defined does not matter. The whole file is read before any type resolution occurs.
|
||||
|
||||
**Functions** have an `in` and `out` dictionary specifying one or more argument names with their corresponding value types. Like with attributes, the types can be specified in place or as a name. Type resolution also works the same except that only value types are checked for.
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
interfaces:
|
||||
Car:
|
||||
attributes:
|
||||
velocity: float
|
||||
door_front_left: Door
|
||||
door_front_right: Door
|
||||
steering_wheel:
|
||||
attributes:
|
||||
angle: float
|
||||
functions:
|
||||
turn: {in: {delta_angle: float32}, out: {final_angle: float32}}
|
||||
Car.Door:
|
||||
attributes:
|
||||
is_open: bool
|
||||
part_of: Car
|
||||
functions:
|
||||
open:
|
||||
close:
|
||||
```
|
||||
|
||||
Let's see how the type resolution of the attibute `Car.Door.part_of: Car` would work here:
|
||||
|
||||
1. Interface `Car.Door.Car` => not found, proceed
|
||||
2. Value type `Car.Door.Car` => not found, proceed
|
||||
3. Interface `Car.Car` => not found, proceed
|
||||
4. Value type `Car.Car` => not found, proceed
|
||||
5. Interface `Car` => found. Link to this interface type.
|
||||
|
||||
|
||||
## Enums
|
||||
|
||||
Enums are values which are associated with a name. They are serialized as 32-bit numbers.
|
||||
|
||||
Enumerators without an explicitly stated numerical value are guaranteed to have an underlying value one larger than that of the preceding enumerator.
|
||||
|
||||
Each enumerator must have a unique value.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
valuetypes:
|
||||
ModeOfTransport:
|
||||
values:
|
||||
Walking:
|
||||
Bicycle:
|
||||
Car: {value: 5}
|
||||
Train:
|
||||
```
|
||||
|
||||
This would be serialized as:
|
||||
- Walking <=> `0x00000000` <=> `0x00 0x00 0x00 0x00`
|
||||
- Bicycle <=> `0x00000001` <=> `0x01 0x00 0x00 0x00`
|
||||
- Car <=> `0x00000005` <=> `0x05 0x00 0x00 0x00`
|
||||
- Train <=> `0x00000006` <=> `0x06 0x00 0x00 0x00`
|
||||
|
||||
## Flagfields
|
||||
|
||||
Flagfields are serialized as 32-bit low endian values where each bit has a named meaning.
|
||||
|
||||
A flag without an explicit bit number is guaranteed to have the bit number of the preceding flag plus one or bit 0 it it's the first in the list.
|
||||
|
||||
Each flag must have a unique bit number.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
valuetypes:
|
||||
Anchor:
|
||||
nullflag: Nowhere
|
||||
flags:
|
||||
Top:
|
||||
Left:
|
||||
Bottom: {bit: 8}
|
||||
Right:
|
||||
```
|
||||
|
||||
This would be serialized as:
|
||||
- Nowhere <=> `0x00000000` <=> `0x00 0x00 0x00 0x00`
|
||||
- Top <=> `0x00000001` <=> `0x01 0x00 0x00 0x00`
|
||||
- Top and Left <=> `0x00000003` <=> `0x03 0x00 0x00 0x00`
|
||||
- Bottom <=> `0x00000100` <=> `0x00 0x01 0x00 0x00`
|
||||
- Top and Bottom and Right <=> `0x00000301` <=> `0x01 0x03 0x00 0x00`
|
||||
Reference in New Issue
Block a user