Merge branch 'devel' into enable-pin

This commit is contained in:
Samuel Sadok
2020-06-10 16:20:31 +02:00
62 changed files with 3978 additions and 1699 deletions
+9 -3
View File
@@ -31,7 +31,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Install ARM GCC and tup (Debian)
- name: Install prerequisites (Debian)
if: startsWith(matrix.os, 'ubuntu-')
run: |
DEBIAN_VERSION="$(lsb_release --release --short)"
@@ -52,11 +52,15 @@ jobs:
sudo apt-get install tup
- name: Install ARM GCC and tup (macOS)
sudo apt install python3 python3-yaml python3-jinja2 python3-jsonschema
- name: Install prerequisites (macOS)
if: startsWith(matrix.os, 'macOS-')
run: |
brew install armmbed/formulae/arm-none-eabi-gcc
brew cask install osxfuse && brew install tup
pip3 install PyYAML Jinja2 jsonschema
- name: Cache chocolatey
uses: actions/cache@v2
@@ -67,7 +71,7 @@ jobs:
restore-keys: |
${{ runner.os }}-gcc-arm-embedded
- name: Install ARM GCC and tup (Windows)
- name: Install prerequisites (Windows)
if: startsWith(matrix.os, 'windows-')
run: |
Invoke-WebRequest -Uri "http://gittup.org/tup/win32/tup-latest.zip" -OutFile ".\tup-latest.zip"
@@ -75,6 +79,8 @@ jobs:
echo "::add-path::$(Resolve-Path .)\tup-latest"
choco install gcc-arm-embedded # downloads https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/gcc-arm-none-eabi-9-2019-q4-major-win32.zip
pip install PyYAML Jinja2 jsonschema
- name: Prepare Compilation
run: |
+5 -1
View File
@@ -12,7 +12,11 @@ sudo: false
addons:
apt:
packages:
libc6-i386
- libc6-i386
- python3
- python3-yaml
- python3-jinja2
- python3-jsonschema
cache:
directories:
+1
View File
@@ -27,6 +27,7 @@ Please add a note of your changes below this heading if you make a Pull Request.
* Using an STM32F405 .svd file allows CortexDebug to view registers during debugging
* Added scripts for building via docker.
* Added ability to change uart baudrate via fibre
* Introduced `odrive-interface.yaml` as a root source for the ODrive's API. `odrivetool` connects much faster as a side effect.
### Changed
* Changed ratiometric `motor.config.current_lim_tolerance` to absolute `motor.config.current_lim_margin`
+1
View File
@@ -1,5 +1,6 @@
#build folder
autogen/
build/
deploy/
.dep/
+10 -10
View File
@@ -25,7 +25,7 @@ Axis::Axis(int axis_num,
sensorless_estimator_(sensorless_estimator),
controller_(controller),
motor_(motor),
trap_(trap),
trap_traj_(trap),
min_endstop_(min_endstop),
max_endstop_(max_endstop)
{
@@ -33,7 +33,7 @@ Axis::Axis(int axis_num,
sensorless_estimator_.axis_ = this;
controller_.axis_ = this;
motor_.axis_ = this;
trap_.axis_ = this;
trap_traj_.axis_ = this;
min_endstop_.axis_ = this;
max_endstop_.axis_ = this;
decode_step_dir_pins();
@@ -194,9 +194,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_
@@ -206,7 +206,7 @@ bool Axis::do_checks() {
// controller_.do_checks();
// Check for endstop presses
bool vel_dependent_stopping = (current_state_ == AXIS_STATE_HOMING) && (controller_.config_.control_mode >= Controller::CTRL_MODE_VELOCITY_CONTROL);
bool vel_dependent_stopping = (current_state_ == AXIS_STATE_HOMING) && (controller_.config_.control_mode >= Controller::CONTROL_MODE_VELOCITY_CONTROL);
if (min_endstop_.config_.enabled && min_endstop_.get_state() && (!vel_dependent_stopping || controller_.vel_setpoint_ < 0.0f)) {
error_ |= ERROR_MIN_ENDSTOP_PRESSED;
} else if (max_endstop_.config_.enabled && max_endstop_.get_state() && (!vel_dependent_stopping || controller_.vel_setpoint_ > 0.0f)) {
@@ -366,8 +366,8 @@ bool Axis::run_closed_loop_control_loop() {
// Slowly drive in the negative direction at homing_speed until the min endstop is pressed
// When pressed, set the linear count to the offset (default 0), and then go to position 0
bool Axis::run_homing() {
Controller::ControlMode_t stored_control_mode = controller_.config_.control_mode;
Controller::InputMode_t stored_input_mode = controller_.config_.input_mode;
Controller::ControlMode stored_control_mode = controller_.config_.control_mode;
Controller::InputMode stored_input_mode = controller_.config_.input_mode;
// TODO: theoretically this check should be inside the update loop,
// otherwise someone could disable the endstop while homing is in progress.
@@ -375,7 +375,7 @@ bool Axis::run_homing() {
return error_ |= ERROR_HOMING_WITHOUT_ENDSTOP, false;
}
controller_.config_.control_mode = Controller::CTRL_MODE_VELOCITY_CONTROL;
controller_.config_.control_mode = Controller::CONTROL_MODE_VELOCITY_CONTROL;
controller_.config_.input_mode = Controller::INPUT_MODE_VEL_RAMP;
controller_.input_pos_ = 0.0f;
@@ -416,7 +416,7 @@ bool Axis::run_homing() {
// Set our current position in encoder counts to make control more logical
encoder_.set_linear_count((int32_t)controller_.pos_setpoint_);
controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL;
controller_.config_.control_mode = Controller::CONTROL_MODE_POSITION_CONTROL;
controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ;
controller_.input_pos_ = 0.0f;
@@ -537,7 +537,7 @@ void Axis::run_state_machine_loop() {
case AXIS_STATE_LOCKIN_SPIN: {
if (!motor_.is_calibrated_ || motor_.config_.direction==0)
goto invalid_state_label;
status = run_lockin_spin(config_.lockin);
status = run_lockin_spin(config_.general_lockin);
} break;
case AXIS_STATE_SENSORLESS_CONTROL: {
+21 -129
View File
@@ -5,43 +5,8 @@
#error "This file should not be included directly. Include odrive_main.h instead."
#endif
class Axis {
class Axis : public ODriveIntf::AxisIntf {
public:
enum Error_t {
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,10 +53,22 @@ public:
LockinConfig_t calibration_lockin = default_calibration();
LockinConfig_t sensorless_ramp = default_sensorless();
LockinConfig_t lockin;
LockinConfig_t general_lockin;
uint32_t can_node_id = 0; // Both axes will have the same id to start
bool can_node_id_extended = false;
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(); }
void set_en_gpio_pin(uint16_t value) {
en_gpio_pin = value;
parent->decode_step_dir_pins();
parent->use_enable_pin_update();
}
void set_use_enable_pin(bool value) { use_enable_pin = value; parent->use_enable_pin_update(); }
void set_enable_pin_active_low(bool value) { enable_pin_active_low = value; parent->use_enable_pin_update(); }
};
struct Homing_t {
@@ -102,13 +79,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,
@@ -149,7 +119,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
@@ -238,7 +208,7 @@ public:
SensorlessEstimator& sensorless_estimator_;
Controller& controller_;
Motor& motor_;
TrapezoidalTrajectory& trap_;
TrapezoidalTrajectory& trap_traj_;
Endstop& min_endstop_;
Endstop& max_endstop_;
@@ -247,7 +217,7 @@ public:
volatile bool thread_id_valid_ = false;
// variables exposed on protocol
Error_t error_ = ERROR_NONE;
Error error_ = ERROR_NONE;
bool step_dir_active_ = false; // auto enabled after calibration, based on config.enable_step_dir
// updated from config in constructor, and on protocol hook
@@ -258,96 +228,18 @@ public:
GPIO_TypeDef* en_port_;
uint16_t en_pin_;
State_t requested_state_ = AXIS_STATE_IDLE;
std::array<State_t, 10> task_chain_ = { AXIS_STATE_UNDEFINED };
State_t& current_state_ = task_chain_.front();
AxisState requested_state_ = AXIS_STATE_IDLE;
std::array<AxisState, 10> task_chain_ = { AXIS_STATE_UNDEFINED };
AxisState& current_state_ = task_chain_.front();
bool startup_sequence_done_ = false;
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", &current_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("use_enable_pin", &config_.use_enable_pin,
[](void* ctx) { static_cast<Axis*>(ctx)->use_enable_pin_update(); }, this),
make_protocol_property("enable_pin_active_low", &config_.enable_pin_active_low,
[](void* ctx) { static_cast<Axis*>(ctx)->use_enable_pin_update(); }, this),
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_property("en_gpio_pin", &config_.en_gpio_pin,
[](void* ctx) {
static_cast<Axis*>(ctx)->decode_step_dir_pins();
static_cast<Axis*>(ctx)->use_enable_pin_update();
}, 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_.lockin.current),
make_protocol_property("ramp_time", &config_.lockin.ramp_time),
make_protocol_property("ramp_distance", &config_.lockin.ramp_distance),
make_protocol_property("accel", &config_.lockin.accel),
make_protocol_property("vel", &config_.lockin.vel),
make_protocol_property("finish_distance", &config_.lockin.finish_distance),
make_protocol_property("finish_on_vel", &config_.lockin.finish_on_vel),
make_protocol_property("finish_on_distance", &config_.lockin.finish_on_distance),
make_protocol_property("finish_on_enc_idx", &config_.lockin.finish_on_enc_idx)),
make_protocol_property("can_node_id", &config_.can_node_id),
make_protocol_property("can_node_id_extended", &config_.can_node_id_extended),
make_protocol_property("can_heartbeat_rate_ms", &config_.can_heartbeat_rate_ms)),
make_protocol_object("motor", motor_.make_protocol_definitions()),
make_protocol_object("controller", controller_.make_protocol_definitions()),
make_protocol_object("encoder", encoder_.make_protocol_definitions()),
make_protocol_object("sensorless_estimator", sensorless_estimator_.make_protocol_definitions()),
make_protocol_object("trap_traj", trap_.make_protocol_definitions()),
make_protocol_object("min_endstop", min_endstop_.make_protocol_definitions()),
make_protocol_object("max_endstop", max_endstop_.make_protocol_definitions()),
make_protocol_function("watchdog_feed", *this, &Axis::watchdog_feed),
make_protocol_function("clear_errors", *this, &Axis::clear_errors)
);
}
};
DEFINE_ENUM_FLAG_OPERATORS(Axis::Error_t)
#endif /* __AXIS_HPP */
+17 -17
View File
@@ -17,7 +17,7 @@ void Controller::reset() {
current_setpoint_ = 0.0f;
}
void Controller::set_error(Error_t error) {
void Controller::set_error(Error error) {
error_ |= error;
axis_->error_ |= Axis::ERROR_CONTROLLER_FAILED;
}
@@ -50,11 +50,11 @@ bool Controller::select_encoder(size_t encoder_num) {
}
void Controller::move_to_pos(float goal_point) {
axis_->trap_.planTrapezoidal(goal_point, pos_setpoint_, vel_setpoint_,
axis_->trap_.config_.vel_limit,
axis_->trap_.config_.accel_limit,
axis_->trap_.config_.decel_limit);
axis_->trap_.t_ = 0.0f;
axis_->trap_traj_.planTrapezoidal(goal_point, pos_setpoint_, vel_setpoint_,
axis_->trap_traj_.config_.vel_limit,
axis_->trap_traj_.config_.accel_limit,
axis_->trap_traj_.config_.decel_limit);
axis_->trap_traj_.t_ = 0.0f;
trajectory_done_ = false;
}
@@ -90,7 +90,7 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate)
config_.anticogging.cogging_map[std::clamp<uint32_t>(config_.anticogging.index++, 0, 3600)] = vel_integrator_current_;
}
if (config_.anticogging.index < 3600) {
config_.control_mode = CTRL_MODE_POSITION_CONTROL;
config_.control_mode = CONTROL_MODE_POSITION_CONTROL;
input_pos_ = config_.anticogging.index * axis_->encoder_.getCoggingRatio();
input_vel_ = 0.0f;
input_current_ = 0.0f;
@@ -98,7 +98,7 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate)
return false;
} else {
config_.anticogging.index = 0;
config_.control_mode = CTRL_MODE_POSITION_CONTROL;
config_.control_mode = CONTROL_MODE_POSITION_CONTROL;
input_pos_ = 0.0f; // Send the motor home
input_vel_ = 0.0f;
input_current_ = 0.0f;
@@ -200,19 +200,19 @@ bool Controller::update(float* current_setpoint_output) {
if (trajectory_done_)
break;
if (axis_->trap_.t_ > axis_->trap_.Tf_) {
if (axis_->trap_traj_.t_ > axis_->trap_traj_.Tf_) {
// Drop into position control mode when done to avoid problems on loop counter delta overflow
config_.control_mode = CTRL_MODE_POSITION_CONTROL;
config_.control_mode = CONTROL_MODE_POSITION_CONTROL;
pos_setpoint_ = input_pos_;
vel_setpoint_ = 0.0f;
current_setpoint_ = 0.0f;
trajectory_done_ = true;
} else {
TrapezoidalTrajectory::Step_t traj_step = axis_->trap_.eval(axis_->trap_.t_);
TrapezoidalTrajectory::Step_t traj_step = axis_->trap_traj_.eval(axis_->trap_traj_.t_);
pos_setpoint_ = traj_step.Y;
vel_setpoint_ = traj_step.Yd;
current_setpoint_ = traj_step.Ydd * config_.inertia;
axis_->trap_.t_ += current_meas_period;
axis_->trap_traj_.t_ += current_meas_period;
}
anticogging_pos = pos_setpoint_; // FF the position setpoint instead of the pos_estimate
} break;
@@ -227,7 +227,7 @@ bool Controller::update(float* current_setpoint_output) {
// TODO Decide if we want to use encoder or pll position here
float gain_scheduling_multiplier = 1.0f;
float vel_des = vel_setpoint_;
if (config_.control_mode >= CTRL_MODE_POSITION_CONTROL) {
if (config_.control_mode >= CONTROL_MODE_POSITION_CONTROL) {
float pos_err;
if (!pos_estimate_src) {
set_error(ERROR_INVALID_ESTIMATE);
@@ -292,12 +292,12 @@ bool Controller::update(float* current_setpoint_output) {
// Anti-cogging is enabled after calibration
// We get the current position and apply a current feed-forward
// ensuring that we handle negative encoder positions properly (-1 == motor->encoder.encoder_cpr - 1)
if (anticogging_valid_ && config_.anticogging.enable) {
if (anticogging_valid_ && config_.anticogging.anticogging_enabled) {
Iq += config_.anticogging.cogging_map[std::clamp(mod((int)anticogging_pos, 3600), 0, 3600)];
}
float v_err = 0.0f;
if (config_.control_mode >= CTRL_MODE_VELOCITY_CONTROL) {
if (config_.control_mode >= CONTROL_MODE_VELOCITY_CONTROL) {
if (!vel_estimate_src) {
set_error(ERROR_INVALID_ESTIMATE);
return false;
@@ -311,7 +311,7 @@ bool Controller::update(float* current_setpoint_output) {
}
// Velocity limiting in current mode
if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL && config_.enable_current_vel_limit) {
if (config_.control_mode < CONTROL_MODE_VELOCITY_CONTROL && config_.enable_current_mode_vel_limit) {
if (!vel_estimate_src) {
set_error(ERROR_INVALID_ESTIMATE);
return false;
@@ -334,7 +334,7 @@ bool Controller::update(float* current_setpoint_output) {
}
// Velocity integrator (behaviour dependent on limiting)
if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL) {
if (config_.control_mode < CONTROL_MODE_VELOCITY_CONTROL) {
// reset integral if not in use
vel_integrator_current_ = 0.0f;
} else {
+13 -87
View File
@@ -5,38 +5,8 @@
#error "This file should not be included directly. Include odrive_main.h instead."
#endif
class Controller {
class Controller : public ODriveIntf::ControllerIntf {
public:
enum Error_t {
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_t{
CTRL_MODE_VOLTAGE_CONTROL = 0,
CTRL_MODE_CURRENT_CONTROL = 1,
CTRL_MODE_VELOCITY_CONTROL = 2,
CTRL_MODE_POSITION_CONTROL = 3
};
enum InputMode_t{
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];
@@ -45,12 +15,12 @@ public:
float calib_pos_threshold = 1.0f;
float calib_vel_threshold = 1.0f;
float cogging_ratio = 1.0f;
bool enable = true;
bool anticogging_enabled = true;
} Anticogging_t;
struct Config_t {
ControlMode_t control_mode = CTRL_MODE_POSITION_CONTROL; //see: ControlMode_t
InputMode_t input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t
ControlMode control_mode = CONTROL_MODE_POSITION_CONTROL; //see: ControlMode_t
InputMode input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t
float pos_gain = 20.0f; // [(counts/s) / counts]
float vel_gain = 5.0f / 10000.0f; // [A/(counts/s)]
// float vel_gain = 5.0f / 200.0f, // [A/(rad/s)] <sensorless example>
@@ -68,15 +38,19 @@ public:
bool enable_gain_scheduling = false;
bool enable_vel_limit = true;
bool enable_overspeed_error = true;
bool enable_current_vel_limit = true; // enable velocity limit in current control mode (requires a valid velocity estimator)
bool enable_current_mode_vel_limit = true; // enable velocity limit in current control mode (requires a valid velocity estimator)
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);
void reset();
void set_error(Error_t error);
void set_error(Error error);
void input_pos_updated();
bool select_encoder(size_t encoder_num);
@@ -95,7 +69,7 @@ public:
Config_t& config_;
Axis* axis_ = nullptr; // set by Axis constructor
Error_t error_ = ERROR_NONE;
Error error_ = ERROR_NONE;
float* pos_estimate_src_ = nullptr;
bool* pos_estimate_valid_src_ = nullptr;
@@ -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", &current_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_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.enable))),
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_t)
#endif // __CONTROLLER_HPP
+7 -7
View File
@@ -35,7 +35,7 @@ void Encoder::setup() {
}
}
void Encoder::set_error(Error_t error) {
void Encoder::set_error(Error error) {
vel_estimate_valid_ = false;
pos_estimate_valid_ = false;
error_ |= error;
@@ -207,7 +207,7 @@ bool Encoder::run_offset_calibration() {
axis_->run_control_loop([&](){
if (!axis_->motor_.enqueue_voltage_timings(voltage_magnitude, 0.0f))
return false; // error set inside enqueue_voltage_timings
axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB);
axis_->motor_.log_timing(TIMING_LOG_ENC_CALIB);
return ++i < start_lock_duration * current_meas_hz;
});
if (axis_->error_ != Axis::ERROR_NONE || axis_->requested_state_ != Axis::AXIS_STATE_UNDEFINED)
@@ -224,7 +224,7 @@ bool Encoder::run_offset_calibration() {
float v_beta = voltage_magnitude * our_arm_sin_f32(phase);
if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta))
return false; // error set inside enqueue_voltage_timings
axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB);
axis_->motor_.log_timing(TIMING_LOG_ENC_CALIB);
encvaluesum += shadow_count_;
@@ -264,7 +264,7 @@ bool Encoder::run_offset_calibration() {
float v_beta = voltage_magnitude * our_arm_sin_f32(phase);
if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta))
return false; // error set inside enqueue_voltage_timings
axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB);
axis_->motor_.log_timing(TIMING_LOG_ENC_CALIB);
encvaluesum += shadow_count_;
@@ -312,7 +312,7 @@ void Encoder::sample_now() {
case MODE_SPI_ABS_CUI:
case MODE_SPI_ABS_AEAT:
{
axis_->motor_.log_timing(Motor::TIMING_LOG_SAMPLE_NOW);
axis_->motor_.log_timing(TIMING_LOG_SAMPLE_NOW);
// Do nothing
} break;
@@ -348,7 +348,7 @@ bool Encoder::abs_spi_init(){
bool Encoder::abs_spi_start_transaction(){
if (mode_ & MODE_FLAG_ABS){
axis_->motor_.log_timing(Motor::TIMING_LOG_SPI_START);
axis_->motor_.log_timing(TIMING_LOG_SPI_START);
if(hw_config_.spi->State != HAL_SPI_STATE_READY){
set_error(ERROR_ABS_SPI_NOT_READY);
return false;
@@ -377,7 +377,7 @@ uint8_t cui_parity(uint16_t v) {
void Encoder::abs_spi_cb(){
HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET);
axis_->motor_.log_timing(Motor::TIMING_LOG_SPI_END);
axis_->motor_.log_timing(TIMING_LOG_SPI_END);
uint16_t pos;
+13 -75
View File
@@ -5,33 +5,12 @@
#error "This file should not be included directly. Include odrive_main.h instead."
#endif
class Encoder {
class Encoder : public ODriveIntf::EncoderIntf {
public:
enum Error_t {
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;
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,13 +32,21 @@ 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,
Config_t& config, const Motor::Config_t& motor_config);
void setup();
void set_error(Error_t error);
void set_error(Error error);
bool do_checks();
void enc_index_cb();
@@ -81,7 +68,7 @@ public:
Config_t& config_;
Axis* axis_ = nullptr; // set by Axis constructor
Error_t error_ = ERROR_NONE;
Error error_ = ERROR_NONE;
bool index_found_ = false;
bool is_ready_ = false;
int32_t shadow_count_ = 0;
@@ -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_t)
#endif // __ENCODER_HPP
+6 -15
View File
@@ -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;
+17 -21
View File
@@ -84,7 +84,7 @@ static uint16_t GPIO_port_samples [2][num_GPIO];
*/
// @brief Floats ALL phases immediately and disarms both motors and the brake resistor.
void low_level_fault(Motor::Error_t error) {
void low_level_fault(Motor::Error error) {
// Disable all motors NOW!
for (size_t i = 0; i < AXIS_COUNT; ++i) {
safety_critical_disarm_motor_pwm(axes[i]->motor_);
@@ -498,9 +498,9 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) {
// Check the timing of the sequencing
if (current_meas_not_DC_CAL)
axis.motor_.log_timing(Motor::TIMING_LOG_ADC_CB_I);
axis.motor_.log_timing(TIMING_LOG_ADC_CB_I);
else
axis.motor_.log_timing(Motor::TIMING_LOG_ADC_CB_DC);
axis.motor_.log_timing(TIMING_LOG_ADC_CB_DC);
bool update_timings = false;
if (hadc == &hadc2) {
@@ -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);
+42 -39
View File
@@ -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;
}
+2 -2
View File
@@ -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;
@@ -137,7 +137,7 @@ bool Motor::check_DRV_fault() {
return true;
}
void Motor::set_error(Motor::Error_t error){
void Motor::set_error(Motor::Error error){
error_ |= error;
axis_->error_ |= Axis::ERROR_MOTOR_FAILED;
safety_critical_disarm_motor_pwm(*this);
+22 -141
View File
@@ -7,35 +7,8 @@
#include "drv8301.h"
class Motor {
class Motor : public ODriveIntf::MotorIntf {
public:
enum Error_t {
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;
@@ -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,29 +62,16 @@ public:
bool acim_autoflux_enable = false;
float acim_autoflux_attack_gain = 10.0f;
float acim_autoflux_decay_gain = 1.0f;
};
enum TimingLog_t {
TIMING_LOG_GENERAL,
TIMING_LOG_ADC_CB_I,
TIMING_LOG_ADC_CB_DC,
TIMING_LOG_MEAS_R,
TIMING_LOG_MEAS_L,
TIMING_LOG_ENC_CALIB,
TIMING_LOG_IDX_SEARCH,
TIMING_LOG_FOC_VOLTAGE,
TIMING_LOG_FOC_CURRENT,
TIMING_LOG_SPI_START,
TIMING_LOG_SAMPLE_NOW,
TIMING_LOG_SPI_END,
TIMING_LOG_NUM_SLOTS
};
enum ArmedState_t {
ARMED_STATE_DISARMED,
ARMED_STATE_WAITING_FOR_TIMINGS,
ARMED_STATE_WAITING_FOR_UPDATE,
ARMED_STATE_ARMED,
// 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(); }
};
Motor(const MotorHardwareConfig_t& hw_config,
@@ -128,7 +88,7 @@ public:
void update_current_controller_gains();
void DRV8301_setup();
bool check_DRV_fault();
void set_error(Error_t error);
void set_error(Error error);
bool do_checks();
float get_inverter_temp();
bool update_thermal_limits(float fet_temp);
@@ -160,13 +120,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_t error_ = ERROR_NONE;
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 +154,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_ = NAN; // [°C] NaN while the ODrive is initializing.
// 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", &current_meas_.phB),
make_protocol_ro_property("current_meas_phC", &current_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", &current_control_.p_gain),
make_protocol_property("i_gain", &current_control_.i_gain),
make_protocol_property("v_current_control_integral_d", &current_control_.v_current_control_integral_d),
make_protocol_property("v_current_control_integral_q", &current_control_.v_current_control_integral_q),
make_protocol_property("Ibus", &current_control_.Ibus),
make_protocol_property("final_v_alpha", &current_control_.final_v_alpha),
make_protocol_property("final_v_beta", &current_control_.final_v_beta),
make_protocol_property("Id_setpoint", &current_control_.Id_setpoint),
make_protocol_ro_property("Iq_setpoint", &current_control_.Iq_setpoint),
make_protocol_property("Iq_measured", &current_control_.Iq_measured),
make_protocol_property("Id_measured", &current_control_.Id_measured),
make_protocol_property("I_measured_report_filter_k", &current_control_.I_measured_report_filter_k),
make_protocol_ro_property("max_allowed_current", &current_control_.max_allowed_current),
make_protocol_ro_property("overcurrent_trip_level", &current_control_.overcurrent_trip_level),
make_protocol_property("acim_rotor_flux", &current_control_.acim_rotor_flux),
make_protocol_ro_property("async_phase_vel", &current_control_.async_phase_vel),
make_protocol_property("async_phase_offset", &current_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_t)
#endif // __MOTOR_HPP
+98 -11
View File
@@ -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;
@@ -177,6 +178,25 @@ inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b) { return reinterpret_cast
inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast<ENUMTYPE>(~static_cast<std::underlying_type_t<ENUMTYPE>>(a)); }
enum TimingLog_t {
TIMING_LOG_GENERAL,
TIMING_LOG_ADC_CB_I,
TIMING_LOG_ADC_CB_DC,
TIMING_LOG_MEAS_R,
TIMING_LOG_MEAS_L,
TIMING_LOG_ENC_CALIB,
TIMING_LOG_IDX_SEARCH,
TIMING_LOG_FOC_VOLTAGE,
TIMING_LOG_FOC_CURRENT,
TIMING_LOG_SPI_START,
TIMING_LOG_SAMPLE_NOW,
TIMING_LOG_SPI_END,
TIMING_LOG_NUM_SLOTS
};
#include "autogen/interfaces.hpp"
// ODrive specific includes
#include <utils.hpp>
#include <gpio_utils.hpp>
@@ -190,12 +210,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 */
+2 -26
View File
@@ -1,13 +1,8 @@
#ifndef __SENSORLESS_ESTIMATOR_HPP
#define __SENSORLESS_ESTIMATOR_HPP
class SensorlessEstimator {
class SensorlessEstimator : public ODriveIntf::SensorlessEstimatorIntf {
public:
enum Error_t {
ERROR_NONE = 0,
ERROR_UNSTABLE_GAIN = 0x01,
};
struct Config_t {
float observer_gain = 1000.0f; // [rad/s]
float pll_bandwidth = 1000.0f; // [rad/s]
@@ -22,7 +17,7 @@ public:
Config_t& config_;
// TODO: expose on protocol
Error_t error_ = ERROR_NONE;
Error error_ = ERROR_NONE;
float phase_ = 0.0f; // [rad]
float pll_pos_ = 0.0f; // [rad]
float vel_estimate_ = 0.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_t)
#endif /* __SENSORLESS_ESTIMATOR_HPP */
-10
View File
@@ -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_;
+3 -3
View File
@@ -5,7 +5,7 @@
#include "communication/can_helpers.hpp"
enum InputMode_t {
enum InputMode {
INPUT_MODE_INACTIVE,
INPUT_MODE_PASSTHROUGH,
INPUT_MODE_VEL_RAMP,
@@ -84,7 +84,7 @@ TEST_SUITE("CAN Functions") {
can_Message_t rxmsg;
rxmsg.buf[0] = INPUT_MODE_MIX_CHANNELS;
rxmsg.buf[1] = INPUT_MODE_PASSTHROUGH;
CHECK(static_cast<InputMode_t>(can_getSignal<InputMode_t>(rxmsg, 0, 8, true, 1, 0)) == INPUT_MODE_MIX_CHANNELS);
CHECK(static_cast<InputMode_t>(can_getSignal<InputMode_t>(rxmsg, 8, 8, true, 1, 0)) == INPUT_MODE_PASSTHROUGH);
CHECK(static_cast<InputMode>(can_getSignal<InputMode>(rxmsg, 0, 8, true, 1, 0)) == INPUT_MODE_MIX_CHANNELS);
CHECK(static_cast<InputMode>(can_getSignal<InputMode>(rxmsg, 8, 8, true, 1, 0)) == INPUT_MODE_PASSTHROUGH);
}
}
+19 -5
View File
@@ -16,6 +16,25 @@ end
python_command = find_python3()
print('Using python command "'..python_command..'"')
run_now("")
tup.frule{inputs={'fibre/cpp/interfaces_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/interfaces.hpp'}
tup.frule{inputs={'fibre/cpp/function_stubs_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/function_stubs.hpp'}
tup.frule{inputs={'fibre/cpp/endpoints_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --generate-endpoints ODrive --template %f --output %o', outputs='autogen/endpoints.hpp'}
tup.frule{inputs={'fibre/cpp/type_info_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/type_info.hpp'}
-- Note: we currently check this file into source control for two reasons:
-- - Don't require tup to run in order to use odrivetool from the repo
-- - On Windows, tup is unhappy with writing outside of the tup directory
-- TODO: use CI to verify that on PRs the enums.py file is consistent with the YAML.
--tup.frule{command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template enums_template.j2 --output ../tools/odrive/enums.py'}
tup.frule{
command=python_command..' ../tools/odrive/version.py --output %o',
outputs={'autogen/version.h'}
}
-- Switch between board versions
boardversion = tup.getconfig("BOARD_VERSION")
if boardversion == "v3.1" then
@@ -101,7 +120,6 @@ if tup.getconfig("STRICT") == "true" then
FLAGS += '-Werror'
end
-- C-specific flags
FLAGS += '-D__weak="__attribute__((weak))"'
FLAGS += '-D__packed="__attribute__((__packed__))"'
@@ -160,10 +178,6 @@ build{
includes=stm_includes
}
tup.frule{
command=python_command..' ../tools/odrive/version.py --output %o',
outputs={'build/version.h'}
}
build{
name='ODriveFirmware',
+1 -1
View File
@@ -80,7 +80,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', 'autogen/type_info.hpp'} -- TODO: fix hack
tup.frule{
inputs= { src, extra_inputs=extra_inputs },
command=compiler..' -c %f '..
+24 -16
View File
@@ -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/type_info.hpp"
#include "communication/interface_can.hpp"
/* Private macros ------------------------------------------------------------*/
/* Private typedef -----------------------------------------------------------*/
/* Global constant data ------------------------------------------------------*/
@@ -25,6 +28,9 @@
#define TO_STR(s) TO_STR_INNER(s)
/* Private variables ---------------------------------------------------------*/
static Introspectable root_obj = ODriveTypeInfo<ODrive>::make_introspectable(odrv);
/* Private function prototypes -----------------------------------------------*/
/* Function implementations --------------------------------------------------*/
@@ -97,7 +103,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
respond(response_channel, use_checksum, "invalid motor %u", motor_number);
} else {
Axis* axis = axes[motor_number];
axis->controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL;
axis->controller_.config_.control_mode = Controller::CONTROL_MODE_POSITION_CONTROL;
axis->controller_.input_pos_ = pos_setpoint;
if (numscan >= 3)
axis->controller_.input_vel_ = vel_feed_forward;
@@ -117,7 +123,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
respond(response_channel, use_checksum, "invalid motor %u", motor_number);
} else {
Axis* axis = axes[motor_number];
axis->controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL;
axis->controller_.config_.control_mode = Controller::CONTROL_MODE_POSITION_CONTROL;
axis->controller_.input_pos_ = pos_setpoint;
if (numscan >= 3)
axis->controller_.config_.vel_limit = vel_limit;
@@ -137,7 +143,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
respond(response_channel, use_checksum, "invalid motor %u", motor_number);
} else {
Axis* axis = axes[motor_number];
axis->controller_.config_.control_mode = Controller::CTRL_MODE_VELOCITY_CONTROL;
axis->controller_.config_.control_mode = Controller::CONTROL_MODE_VELOCITY_CONTROL;
axis->controller_.input_vel_ = vel_setpoint;
if (numscan >= 3)
axis->controller_.input_current_ = current_feed_forward;
@@ -154,7 +160,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
respond(response_channel, use_checksum, "invalid motor %u", motor_number);
} else {
Axis* axis = axes[motor_number];
axis->controller_.config_.control_mode = Controller::CTRL_MODE_CURRENT_CONTROL;
axis->controller_.config_.control_mode = Controller::CONTROL_MODE_CURRENT_CONTROL;
axis->controller_.input_current_ = current_setpoint;
axis->watchdog_feed();
}
@@ -208,17 +214,17 @@ 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();
}
} else if (cmd[0] == 'r') { // read property
@@ -227,12 +233,13 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
if (numscan < 1) {
respond(response_channel, use_checksum, "invalid command format");
} else {
Endpoint* endpoint = application_endpoints_->get_by_name(name, sizeof(name));
if (!endpoint) {
Introspectable property = root_obj.get_child(name, sizeof(name));
const StringConvertibleTypeInfo* type_info = dynamic_cast<const StringConvertibleTypeInfo*>(property.get_type_info());
if (!type_info) {
respond(response_channel, use_checksum, "invalid property");
} else {
char response[10];
bool success = endpoint->get_string(response, sizeof(response));
bool success = type_info->get_string(property, response, sizeof(response));
if (!success)
respond(response_channel, use_checksum, "not implemented");
else
@@ -247,11 +254,12 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
if (numscan < 1) {
respond(response_channel, use_checksum, "invalid command format");
} else {
Endpoint* endpoint = application_endpoints_->get_by_name(name, sizeof(name));
if (!endpoint) {
Introspectable property = root_obj.get_child(name, sizeof(name));
const StringConvertibleTypeInfo* type_info = dynamic_cast<const StringConvertibleTypeInfo*>(property.get_type_info());
if (!type_info) {
respond(response_channel, use_checksum, "invalid property");
} else {
bool success = endpoint->set_string(value, sizeof(value));
bool success = type_info->set_string(property, value, sizeof(value));
if (!success)
respond(response_channel, use_checksum, "not implemented");
}
+6 -6
View File
@@ -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
@@ -295,8 +295,8 @@ void CANSimple::set_input_current_callback(Axis* axis, can_Message_t& msg) {
}
void CANSimple::set_controller_modes_callback(Axis* axis, can_Message_t& msg) {
axis->controller_.config_.control_mode = static_cast<Controller::ControlMode_t>(can_getSignal<int32_t>(msg, 0, 32, true));
axis->controller_.config_.input_mode = static_cast<Controller::InputMode_t>(can_getSignal<int32_t>(msg, 32, 32, true));
axis->controller_.config_.control_mode = static_cast<Controller::ControlMode>(can_getSignal<int32_t>(msg, 0, 32, true));
axis->controller_.config_.input_mode = static_cast<Controller::InputMode>(can_getSignal<int32_t>(msg, 32, 32, true));
}
void CANSimple::set_vel_limit_callback(Axis* axis, can_Message_t& msg) {
@@ -308,12 +308,12 @@ void CANSimple::start_anticogging_callback(Axis* axis, can_Message_t& msg) {
}
void CANSimple::set_traj_vel_limit_callback(Axis* axis, can_Message_t& msg) {
axis->trap_.config_.vel_limit = can_getSignal<float>(msg, 0, 32, true);
axis->trap_traj_.config_.vel_limit = can_getSignal<float>(msg, 0, 32, true);
}
void CANSimple::set_traj_accel_limits_callback(Axis* axis, can_Message_t& msg) {
axis->trap_.config_.accel_limit = can_getSignal<float>(msg, 0, 32, true);
axis->trap_.config_.decel_limit = can_getSignal<float>(msg, 32, 32, true);
axis->trap_traj_.config_.accel_limit = can_getSignal<float>(msg, 0, 32, true);
axis->trap_traj_.config_.decel_limit = can_getSignal<float>(msg, 32, 32, true);
}
void CANSimple::set_traj_A_per_css_callback(Axis* axis, can_Message_t& msg) {
+8 -155
View File
@@ -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,9 @@ int _write(int file, const char* data, int len) {
#endif
return len;
}
#include "../autogen/function_stubs.hpp"
ODrive& ep_root = odrv;
#include "../autogen/endpoints.hpp"
-4
View File
@@ -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);
+11 -11
View File
@@ -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 PROTOCOL_SIMPLE:
CANSimple::handle_can_message(rxmsg);
break;
}
@@ -57,7 +57,7 @@ static void can_server_thread_wrapper(void *ctx) {
bool ODriveCAN::start_can_server() {
HAL_StatusTypeDef status;
set_baud_rate(config_.baud);
set_baud_rate(config_.baud_rate);
status = HAL_CAN_Init(handle_);
@@ -136,25 +136,25 @@ void ODriveCAN::set_baud_rate(uint32_t baudRate) {
switch (baudRate) {
case CAN_BAUD_125K:
handle_->Init.Prescaler = 16; // 21 TQ's
config_.baud = baudRate;
config_.baud_rate = baudRate;
reinit_can();
break;
case CAN_BAUD_250K:
handle_->Init.Prescaler = 8; // 21 TQ's
config_.baud = baudRate;
config_.baud_rate = baudRate;
reinit_can();
break;
case CAN_BAUD_500K:
handle_->Init.Prescaler = 4; // 21 TQ's
config_.baud = baudRate;
config_.baud_rate = baudRate;
reinit_can();
break;
case CAN_BAUD_1000K:
handle_->Init.Prescaler = 2; // 21 TQ's
config_.baud = baudRate;
config_.baud_rate = baudRate;
reinit_can();
break;
@@ -172,7 +172,7 @@ void ODriveCAN::reinit_can() {
status = HAL_CAN_ActivateNotification(handle_, CAN_IT_RX_FIFO0_MSG_PENDING);
}
void ODriveCAN::set_error(Error_t error) {
void ODriveCAN::set_error(Error error) {
error_ |= error;
}
// This function is called by each axis.
@@ -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 PROTOCOL_SIMPLE:
CANSimple::send_heartbeat(axis);
break;
}
+9 -30
View File
@@ -19,28 +19,19 @@ enum {
CAN_BAUD_1M = 1000000
};
enum CAN_Protocol_t {
CAN_PROTOCOL_SIMPLE
};
class ODriveCAN {
class ODriveCAN : public ODriveIntf::CanIntf {
public:
struct Config_t {
uint32_t baud = CAN_BAUD_250K;
CAN_Protocol_t protocol = CAN_PROTOCOL_SIMPLE;
uint32_t baud_rate = CAN_BAUD_250K;
Protocol protocol = PROTOCOL_SIMPLE;
};
enum Error_t {
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_;
const uint32_t stack_size_ = 1024; // Bytes
Error_t error_ = ERROR_NONE;
Error error_ = ERROR_NONE;
volatile bool thread_id_valid_ = false;
bool start_can_server();
@@ -48,31 +39,19 @@ class ODriveCAN {
void send_heartbeat(Axis *axis);
void reinit_can();
void set_error(Error_t error);
void set_error(Error error);
// I/O Functions
uint32_t available();
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)),
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_t)
#endif // __INTERFACE_CAN_HPP
+1 -1
View File
@@ -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 {
+90
View File
@@ -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 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
#include <fibre/introspection.hpp>
// Note: with -Og the functions with large switch statements reserves a huge amount
// of stack space because they 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`, `-O3` and `-Os` are supersets of this.
#pragma GCC push_options
#pragma GCC optimize ("s")
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);
static void get_property(Introspectable& result, size_t idx) {
switch (idx) {
[%- for endpoint in endpoints %]
[%- if endpoint.function.name == 'exchange' and endpoint.in_bindings | list == ['obj'] %]
case [[endpoint.id]]: { [[(endpoint.in_bindings['obj'] + '$') | replace(')$', ', &result.storage_)')]]; result.type_info_ = &FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_name]]>::singleton; } break;
[%- endif %]
[%- endfor %]
default: break;
}
}
bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) {
//Introspectable property = get_property(idx);
//if property.is_valid()
switch (idx) {
[%- for endpoint in endpoints %]
[%- if (endpoint.function.name == 'exchange' or endpoint.function.name == 'read') and endpoint.in_bindings | list == ['obj'] %]
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_name]]>([[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_name]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break;
[%- else %]
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_name]]>([[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_name]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break;
[%- endif %]
[%- 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;
}
Introspectable property{};
get_property(property, endpoint_ref.endpoint_id);
const FloatSettableTypeInfo* type_info = dynamic_cast<const FloatSettableTypeInfo*>(property.get_type_info());
return type_info && type_info->set_float(property, value);
}
}
#pragma GCC pop_options
#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_name]]> in_[[arg.name]], [% endfor %][% for arg in func.out.values() %][[arg.type.c_name]]* 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_name]]>::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_name]][[', ' 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_name]][[', ' 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_name]]>::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
@@ -0,0 +1,219 @@
#ifndef __FIBRE_INTROSPECTION_HPP
#define __FIBRE_INTROSPECTION_HPP
#include <stdlib.h>
#include <algorithm>
#include <cstring>
#pragma GCC push_options
#pragma GCC optimize ("s")
class TypeInfo;
class Introspectable;
using introspectable_storage_t = std::aligned_storage<16, 4>::type;
struct PropertyInfo {
const char * name;
const TypeInfo* type_info;
};
/**
* @brief Contains runtime accessible type information.
*
* Specifically, this information consists of a list of PropertyInfo items which
* enable accessing attributes of an object by a runtime string.
*
* Typically, for each combination of C++ type and Fibre interface implemented
* by this type, one (static constant) TypeInfo object will exist.
*/
class TypeInfo {
friend class Introspectable;
public:
TypeInfo(const PropertyInfo* property_table, size_t property_table_length)
: property_table_(property_table), property_table_length_(property_table_length) {}
virtual introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const = 0;
Introspectable get_child(const Introspectable& obj, const char * name, size_t length) const;
protected:
template<typename T> static T& as(Introspectable& obj);
template<typename T> static const T& as(const Introspectable& obj);
template<typename T> static Introspectable make_introspectable(T obj, const TypeInfo* type_info);
private:
const PropertyInfo* property_table_;
size_t property_table_length_;
};
/**
* @brief Wraps a reference to an application object by attaching runtime
* accessible type information.
*
* The reference that is wrapped is typically a pointer but can also be a small
* temporary, on-demand constructed object such as a fibre::Property<...> which
* contains multiple pointers.
*/
class Introspectable {
friend class TypeInfo;
public:
Introspectable() {}
/**
* @brief Returns an Introspectable object for the attribute referenced by
* the specified attribute name.
*
* The name can consist of multiple parts separated by dots.
*
* If the attribute does not exist, an invalid Introspectable is returned.
*
* @param path: The name or path of the attribute.
* @param length: The maximum length of the name.
*/
Introspectable get_child(const char * path, size_t length) {
Introspectable current = *this;
const char * begin = path;
const char * end = std::find(begin, path + length, '\0');
while ((begin < end) && current.type_info_) {
const char * end_of_token = std::find(begin, end, '.');
current = current.get_direct_child(begin, end_of_token - begin);
begin = std::min(end, end_of_token + 1);
}
return current;
};
bool is_valid() {
return type_info_;
}
const TypeInfo* get_type_info() {
return type_info_;
}
private:
Introspectable get_direct_child(const char * name, size_t length) const {
for (size_t i = 0; i < type_info_->property_table_length_; ++i) {
if (!strncmp(name, type_info_->property_table_[i].name, length)) {
Introspectable result;
result.storage_ = type_info_->get_child(storage_, i);
result.type_info_ = type_info_->property_table_[i].type_info;
return result;
}
}
return {};
}
public: // these should technically be protected but are public for optimization reasons
// We use this storage to hold generic small objects. Usually that's a pointer
// but sometimes it's an on-demand constructed Property<...>.
// Caution: only put objects in here which are trivially copyable, movable
// and destructible as any custom operation wouldn't be called.
introspectable_storage_t storage_;
const TypeInfo* type_info_ = nullptr;
};
template<typename T> T& TypeInfo::as(Introspectable& obj) {
static_assert(sizeof(T) <= sizeof(obj.storage_));
return *(T*)&obj.storage_;
}
template<typename T> const T& TypeInfo::as(const Introspectable& obj) {
static_assert(sizeof(T) <= sizeof(obj.storage_));
return *(const T*)&obj.storage_;
}
template<typename T> Introspectable TypeInfo::make_introspectable(T obj, const TypeInfo* type_info) {
Introspectable introspectable;
as<T>(introspectable) = obj;
introspectable.type_info_ = type_info;
return introspectable;
}
// maybe_underlying_type_t<T> resolves to the underlying type of T if T is an enum type or otherwise to T itself.
template<typename T, bool = std::is_enum<T>::value> struct maybe_underlying_type;
template<typename T> struct maybe_underlying_type<T, true> { typedef std::underlying_type_t<T> type; };
template<typename T> struct maybe_underlying_type<T, false> { typedef T type; };
template<typename T> using maybe_underlying_type_t = typename maybe_underlying_type<T>::type;
struct StringConvertibleTypeInfo {
virtual bool get_string(const Introspectable& obj, char* buffer, size_t length) const { return false; }
virtual bool set_string(const Introspectable& obj, char* buffer, size_t length) const { return false; }
};
struct FloatSettableTypeInfo {
//virtual bool get_float(const Introspectable& obj, float* val) const { return false; }
virtual bool set_float(const Introspectable& obj, float val) const { return false; }
};
/* Built-in type infos ********************************************************/
template<typename T>
struct FibrePropertyTypeInfo;
// readonly property
template<typename T>
struct FibrePropertyTypeInfo<Property<const T>> : StringConvertibleTypeInfo, TypeInfo {
using TypeInfo::TypeInfo;
static const PropertyInfo property_table[];
static const FibrePropertyTypeInfo<Property<const T>> singleton;
introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const override {
return {};
}
bool get_string(const Introspectable& obj, char* buffer, size_t length) const override {
return to_string(static_cast<maybe_underlying_type_t<T>>(as<const Property<const T>>(obj).read()), buffer, length, 0);
}
};
template<typename T>
const PropertyInfo FibrePropertyTypeInfo<Property<const T>>::property_table[] = {};
template<typename T>
const FibrePropertyTypeInfo<Property<const T>> FibrePropertyTypeInfo<Property<const T>>::singleton{FibrePropertyTypeInfo<Property<const T>>::property_table, sizeof(FibrePropertyTypeInfo<Property<const T>>::property_table) / sizeof(FibrePropertyTypeInfo<Property<const T>>::property_table[0])};
// readwrite property
template<typename T>
struct FibrePropertyTypeInfo<Property<T>> : FloatSettableTypeInfo, StringConvertibleTypeInfo, TypeInfo {
using TypeInfo::TypeInfo;
static const PropertyInfo property_table[];
static const FibrePropertyTypeInfo<Property<T>> singleton;
static const Introspectable make_introspectable(Property<T> obj) { return TypeInfo::make_introspectable(obj, &singleton); }
introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const override {
return {};
}
bool get_string(const Introspectable& obj, char* buffer, size_t length) const override {
return to_string(static_cast<maybe_underlying_type_t<T>>(as<const Property<T>>(obj).read()), buffer, length, 0);
}
bool set_string(const Introspectable& obj, char* buffer, size_t length) const override {
maybe_underlying_type_t<T> value;
if (!from_string(buffer, length, &value, 0)) {
return false;
}
as<const Property<T>>(obj).exchange(static_cast<T>(value));
return true;
}
bool set_float(const Introspectable& obj, float val) const override {
maybe_underlying_type_t<T> value;
if (!conversion::set_from_float(val, &value)) {
return false;
}
as<const Property<T>>(obj).exchange(static_cast<T>(value));
return true;
}
};
template<typename T>
const PropertyInfo FibrePropertyTypeInfo<Property<T>>::property_table[] = {};
template<typename T>
const FibrePropertyTypeInfo<Property<T>> FibrePropertyTypeInfo<Property<T>>::singleton{FibrePropertyTypeInfo<Property<T>>::property_table, sizeof(FibrePropertyTypeInfo<Property<T>>::property_table) / sizeof(FibrePropertyTypeInfo<Property<T>>::property_table[0])};
#pragma GCC pop_options
#endif // __FIBRE_INTROSPECTION_HPP
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
+94
View File
@@ -0,0 +1,94 @@
/*[# 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.
*
*/
#pragma GCC push_options
#pragma GCC optimize ("s")
[%- macro rettype(func) %]
[%- if not func.out -%]
void
[%- elif func.out | length == 1 -%]
[[(func.out.values() | first).type.c_name]]
[%- 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 property in intf.attributes.values() %]
[%- if property.type.fullname.startswith("fibre.Property") %]
[%- if not property.c_getter and not property.c_setter %]
template<typename T> static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{&obj->[[property.c_name]]}; }
template<typename T> static inline void get_[[property.name]](T* obj, void* ptr) { new (ptr) [[property.type.c_name]]{&obj->[[property.c_name]]}; }[# these are for the set_endpoint_from_float function. This is unmaintainable and should go away #]
[%- elif not property.c_setter %]
template<typename T> static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }}; }
template<typename T> static inline void get_[[property.name]](T* obj, void* ptr) { new (ptr) [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }}; }
[%- else %]
template<typename T> static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_name]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; }
template<typename T> static inline void get_[[property.name]](T* obj, void* ptr) { new (ptr) [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_name]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; }
[%- endif %]
[%- else %]
template<typename T> static inline auto get_[[property.name]](T* obj) { return &obj->[[property.c_name]]; }
[%- endif %]
[%- 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_name]] [[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_name]] [[func.name | to_snake_case]]_in_[[arg.name]]_; // for internal use by Fibre
template<typename T> static auto get_[[func.name | to_snake_case]]_in_[[arg.name]]_(T* obj) { return Property<[[arg.type.c_name]]>{&obj->[[func.name | to_snake_case]]_in_[[arg.name]]_}; }
template<typename T> static void get_[[func.name | to_snake_case]]_in_[[arg.name]]_(T* obj, void* ptr) { new (ptr) Property<[[arg.type.c_name]]>{&obj->[[func.name | to_snake_case]]_in_[[arg.name]]_}; }
[%- endfor %]
[%- for k, arg in func.out.items() %]
[[arg.type.c_name]] [[func.name | to_snake_case]]_out_[[arg.name]]_; // for internal use by Fibre
template<typename T> static auto get_[[func.name | to_snake_case]]_out_[[arg.name]]_(T* obj) { return Property<const [[arg.type.c_name]]>{&obj->[[func.name | to_snake_case]]_out_[[arg.name]]_}; }
template<typename T> static void get_[[func.name | to_snake_case]]_out_[[arg.name]]_(T* obj, void* ptr) { new (ptr) Property<const [[arg.type.c_name]]>{&obj->[[func.name | to_snake_case]]_out_[[arg.name]]_}; }
[%- 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_name]] operator | ([[enum.c_name]] a, [[enum.c_name]] b) { return static_cast<[[enum.c_name]]>(static_cast<std::underlying_type_t<[[enum.c_name]]>>(a) | static_cast<std::underlying_type_t<[[enum.c_name]]>>(b)); }
inline [[enum.c_name]] operator & ([[enum.c_name]] a, [[enum.c_name]] b) { return static_cast<[[enum.c_name]]>(static_cast<std::underlying_type_t<[[enum.c_name]]>>(a) & static_cast<std::underlying_type_t<[[enum.c_name]]>>(b)); }
inline [[enum.c_name]] operator ^ ([[enum.c_name]] a, [[enum.c_name]] b) { return static_cast<[[enum.c_name]]>(static_cast<std::underlying_type_t<[[enum.c_name]]>>(a) ^ static_cast<std::underlying_type_t<[[enum.c_name]]>>(b)); }
inline [[enum.c_name]]& operator |= ([[enum.c_name]] &a, [[enum.c_name]] b) { return reinterpret_cast<[[enum.c_name]]&>(reinterpret_cast<std::underlying_type_t<[[enum.c_name]]>&>(a) |= static_cast<std::underlying_type_t<[[enum.c_name]]>>(b)); }
inline [[enum.c_name]]& operator &= ([[enum.c_name]] &a, [[enum.c_name]] b) { return reinterpret_cast<[[enum.c_name]]&>(reinterpret_cast<std::underlying_type_t<[[enum.c_name]]>&>(a) &= static_cast<std::underlying_type_t<[[enum.c_name]]>>(b)); }
inline [[enum.c_name]]& operator ^= ([[enum.c_name]] &a, [[enum.c_name]] b) { return reinterpret_cast<[[enum.c_name]]&>(reinterpret_cast<std::underlying_type_t<[[enum.c_name]]>&>(a) ^= static_cast<std::underlying_type_t<[[enum.c_name]]>>(b)); }
inline [[enum.c_name]] operator ~ ([[enum.c_name]] a) { return static_cast<[[enum.c_name]]>(~static_cast<std::underlying_type_t<[[enum.c_name]]>>(a)); }
[%- endif %]
[%- endfor %]
#pragma GCC pop_options
+22 -69
View File
@@ -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;
}
+50
View File
@@ -0,0 +1,50 @@
/*[# 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.
*/
#include <fibre/introspection.hpp>
#pragma GCC push_options
#pragma GCC optimize ("s")
[% for intf in interfaces.values() %][% if not intf.builtin %]
template<typename T>
struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo {
using TypeInfo::TypeInfo;
static const PropertyInfo property_table[];
static const [[intf.fullname | to_pascal_case]]TypeInfo<T> singleton;
static Introspectable make_introspectable(T& obj) { return TypeInfo::make_introspectable(&obj, &singleton); }
introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const override {
T* ptr = *(T**)&obj;
introspectable_storage_t res;
switch (idx) {
[%- for property in intf.attributes.values() %]
case [[loop.index0]]: *(decltype([[intf.c_name]]::get_[[property.name]](std::declval<T*>()))*)(&res) = [[intf.c_name]]::get_[[property.name]](ptr); break;
[%- endfor %]
}
return res;
}
};
[% endif %][% endfor %]
[% for intf in interfaces.values() %][% if not intf.builtin %]
template<typename T>
const PropertyInfo [[intf.fullname | to_pascal_case]]TypeInfo<T>::property_table[] = {
[%- for property in intf.attributes.values() %]
{"[[property.name]]", &[[(property.type.purename or property.type.fullname) | to_pascal_case]]TypeInfo<std::remove_reference_t<decltype(*[[intf.c_name]]::get_[[property.name]](std::declval<T*>()))>>::singleton},
[%- endfor %]
};
template<typename T>
const [[intf.fullname | to_pascal_case]]TypeInfo<T> [[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])};
[% endif %][% endfor %]
#pragma GCC pop_options
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
#!/bin/python3
import sys
import os
try:
exec(open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'fibre', 'tools', 'interface_generator.py')).read())
except ImportError as ex:
print(str(ex), file=sys.stderr)
print("Note that there are new compile-time dependencies since around v0.5.1.", file=sys.stderr)
print("Check out https://github.com/madcowswe/ODrive/blob/devel/docs/developer-guide.md#prerequisites for details.", file=sys.stderr)
exit(1)
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -65,10 +65,10 @@ See [state machine](#state-machine) for a description of each state.
The default control mode is position control.
If you want a different mode, you can change `<axis>.controller.config.control_mode`.
Possible values are:
* `CTRL_MODE_POSITION_CONTROL`
* `CTRL_MODE_VELOCITY_CONTROL`
* `CTRL_MODE_CURRENT_CONTROL`
* `CTRL_MODE_VOLTAGE_CONTROL` - this one is not normally used.
* `CONTROL_MODE_POSITION_CONTROL`
* `CONTROL_MODE_VELOCITY_CONTROL`
* `CONTROL_MODE_CURRENT_CONTROL`
* `CONTROL_MODE_VOLTAGE_CONTROL` - this one is not normally used.
### Input Mode
The default input mode is `INPUT_MODE_PASSTHROUGH`.
+6 -3
View File
@@ -35,7 +35,7 @@ The recommended tools for ODrive development are:
* **ARM GNU Compiler**: For cross-compiling code
* **ARM GDB**: For debugging the code and stepping through on the device
* **OpenOCD**: For flashing the ODrive with the STLink/v2 programmer
* **Python**: For running the Python tools (`odrivetool`). Also required for compiling firmware.
* **Python 3**, along with the packages `PyYAML`, `Jinja2` and `jsonschema`: For running the Python tools (`odrivetool`). Also required for compiling firmware.
See below for specific installation instructions for your OS.
@@ -57,6 +57,7 @@ sudo apt-get update
sudo apt-get install gcc-arm-embedded
sudo apt-get install openocd
sudo add-apt-repository ppa:jonathonf/tup && sudo apt-get update && sudo apt-get install tup
sudo apt-get install python3 python3-yaml python3-jinja2 python3-jsonschema
```
#### Linux (Ubuntu >= 20.04)
@@ -64,6 +65,7 @@ sudo add-apt-repository ppa:jonathonf/tup && sudo apt-get update && sudo apt-get
sudo apt install gcc-arm-embedded
sudo apt install openocd
sudo apt install tup
sudo apt install python3 python3-yaml python3-jinja2 python3-jsonschema
```
#### Arch Linux
@@ -71,6 +73,7 @@ sudo apt install tup
sudo pacman -S arm-none-eabi-gcc arm-none-eabi-binutils
sudo pacman -S arm-none-eabi-gdb
sudo pacman -S tup
sudo pacman -S python python-yaml python-jinja python-jsonschema
```
* [OpenOCD AUR package](https://aur.archlinux.org/packages/openocd/)
@@ -80,6 +83,7 @@ First install [Homebrew](https://brew.sh/). Then you can run these commands in T
brew install armmbed/formulae/arm-none-eabi-gcc
brew cask install osxfuse && brew install tup
brew install openocd
pip3 install PyYAML Jinja2 jsonschema
```
#### Windows
@@ -93,6 +97,7 @@ Some instructions in this document may assume that you're using a bash command p
* [Tup](http://gittup.org/tup/index.html)
* [GNU MCU Eclipse's Windows Build Tools](https://github.com/gnu-mcu-eclipse/windows-build-tools/releases)
* [Python 3](https://www.python.org/downloads/)
* Install Python packages: `pip install PyYAML Jinja2 jsonschema`
* [OpenOCD](https://github.com/xpack-dev-tools/openocd-xpack/releases/).
* [ST-Link/V2 Drivers](http://www.st.com/web/en/catalog/tools/FM147/SC1887/PF260219)
@@ -127,8 +132,6 @@ You can also modify the compile-time defaults for all `.config` parameters. You
2. Connect the ODrive via USB and power it up.
3. Flash the firmware using [odrivetool dfu](odrivetool#device-firmware-update).
If you get `/bin/sh: 1: python: not found` while running `make`, change the tup file command to use `python3`
### Flashing using an STLink/v2 programmer
* Connect `GND`, `SWD`, and `SWC` on connector J2 to the programmer. Note: Always plug in `GND` first!
+4 -4
View File
@@ -337,20 +337,20 @@ Note that in this mode `encoder.pos_cpr` is used for feedback in stead of `encod
If you try to increment the axis with a large step in one go that exceeds `cpr/2` steps, the motor will go to the same angle around the wrong way. This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a virtual CPR that is an integer times larger than your encoder's actual CPR. Set `encoder.config.cpr = N * your_enc_cpr`, where N is some integer. Choose N to give you an appropriate circular space for your application.
### Velocity control
Set `axis.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL`.<br>
Set `axis.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL`.<br>
You can now control the velocity with `axis.controller.input_vel = 5000` [count/s].
### Ramped velocity control
Set `axis.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL`.<br>
Set `axis.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL`.<br>
Set the velocity ramp rate (acceleration): `axis.controller.config.vel_ramp_rate = 2000` [counts/s^2]<br>
Activate the ramped velocity mode: `axis.controller.config.input_mode = INPUT_MODE_VEL_RAMP`.<br>
You can now control the velocity with `axis.controller.input_vel = 5000` [count/s].
### Current control
Set `axis.controller.config.control_mode = CTRL_MODE_CURRENT_CONTROL`.<br>
Set `axis.controller.config.control_mode = CONTROL_MODE_CURRENT_CONTROL`.<br>
You can now control the current with `axis.controller.input_current = 3` [A].
Note: If you exceed `vel_limit` in current control mode, the current is reduced. To disable this, set `axis.controller.enable_current_vel_limit = False`.
Note: If you exceed `vel_limit` in current control mode, the current is reduced. To disable this, set `axis.controller.enable_current_mode_vel_limit = False`.
## Watchdog Timer
Each axis has a configurable watchdog timer that can stop the motors if the
+1 -1
View File
@@ -52,7 +52,7 @@ odrv0.axis0.controller.config.pos_gain = 1
odrv0.axis0.controller.config.vel_gain = 0.02
odrv0.axis0.controller.config.vel_integrator_gain = 0.1
odrv0.axis0.controller.config.vel_limit = 1000
odrv0.axis0.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL
odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL
```
In the next step we are going to start powering the motor and so we want to make sure that some of the above settings that require a reboot are applied first.
+9 -9
View File
@@ -30,10 +30,10 @@ Pass `input_xxx` through to `xxx_setpoint` directly.
* `input_current`
### Valid Control modes:
* `CTRL_MODE_VOLTAGE_CONTROL`
* `CTRL_MODE_CURRENT_CONTROL`
* `CTRL_MODE_VELOCITY_CONTROL`
* `CTRL_MODE_POSITION_CONTROL`
* `CONTROL_MODE_VOLTAGE_CONTROL`
* `CONTROL_MODE_CURRENT_CONTROL`
* `CONTROL_MODE_VELOCITY_CONTROL`
* `CONTROL_MODE_POSITION_CONTROL`
## INPUT_MODE_VEL_RAMP
Ramps a velocity command from the current value to the target value.
@@ -46,7 +46,7 @@ Ramps a velocity command from the current value to the target value.
* `input_vel`
### Valid Control Modes:
* `CTRL_MODE_VELOCITY_CONTROL`
* `CONTROL_MODE_VELOCITY_CONTROL`
## INPUT_MODE_POS_FILTER
Implements a 2nd order position tracking filter. Inteded for use with step/dir interface, but can also be used with position-only commands.
@@ -62,7 +62,7 @@ Result of a step command from 1000 to 0
* `input_pos`
### Valid Control modes:
* `CTRL_MODE_POSITION_CONTROL`
* `CONTROL_MODE_POSITION_CONTROL`
## INPUT_MODE_MIX_CHANNELS
Not Implemented.
@@ -83,7 +83,7 @@ Implementes an online trapezoidal trajectory planner.
* `input_pos`
### Valid Control Modes:
* `CTRL_MODE_POSITION_CONTROL`
* `CONTROL_MODE_POSITION_CONTROL`
## INPUT_MODE_CURRENT_RAMP
Ramp a current command from the current value to the target value.
@@ -95,7 +95,7 @@ Ramp a current command from the current value to the target value.
* `input_current`
### Valid Control Modes:
* `CTRL_MODE_CURRENT_CONTROL`
* `CONTROL_MODE_CURRENT_CONTROL`
## INPUT_MODE_MIRROR
Implements "electronic mirroring". This is like electronic camming, but you can only mirror exactly the movements of the other motor, according to a fixed ratio
@@ -110,4 +110,4 @@ Implements "electronic mirroring". This is like electronic camming, but you can
* None. Inputs are taken directly from the other axis encoder estimates
### Valid Control modes
* `CTRL_MODE_POSITION_CONTROL`
* `CONTROL_MODE_POSITION_CONTROL`
+133
View File
@@ -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`
+1
View File
@@ -17,6 +17,7 @@ The testing facility consists of the following components:
- `can_test.py`: Partial coverage of the commands described in [CAN Protocol](can-protocol)
- `closed_loop_test.py`: Velocity control, position control (TODO: sensorless control), brake regen current hard limit, current control with velocity limiting
- `encoder_test.py`: Incremental encoder, hall effect encoder, sin/cos encoder, SPI encoders (AMS, CUI)
- `fibre_test.py`: General USB protocol tests
- `nvm_test.py`: Configuration storage
- `pwm_input_test.py`: PWM input
- `step_dir_test.py`: Step/dir input
+15
View File
@@ -0,0 +1,15 @@
# TODO: This file is dangerous because the enums could potentially change between API versions. Should transmit as part of the JSON.
# To regenerate this file, nagivate to the top level of the ODrive repository and run:
# python Firmware/interface_generator_stub.py --definitions Firmware/odrive-interface.yaml --template tools/enums_template.j2 --output tools/odrive/enums.py
[%- for _, enum in value_types.items() %]
[%- if enum.is_enum %]
# [[enum.fullname]]
[%- for k, value in enum['values'].items() %]
[[(((enum.parent.name if enum.name in ['Error', 'Mode'] else '') + enum.name + k) | to_macro_case).ljust(40)]] = [% if enum.is_flags %]0x[['%08x' | format(value.value)]][% else %][[value.value]][% endif %]
[%- endfor %]
[%- endif %]
[%- endfor %]
+133 -87
View File
@@ -1,97 +1,143 @@
# TODO: This is dangerous. Transmit as part of the JSON
# TODO: This file is dangerous because the enums could potentially change between API versions. Should transmit as part of the JSON.
# To regenerate this file, nagivate to the top level of the ODrive repository and run:
# python Firmware/interface_generator_stub.py --definitions Firmware/odrive-interface.yaml --template tools/enums_template.j2 --output tools/odrive/enums.py
AXIS_STATE_UNDEFINED = 0
AXIS_STATE_IDLE = 1
AXIS_STATE_STARTUP_SEQUENCE = 2
AXIS_STATE_FULL_CALIBRATION_SEQUENCE = 3
AXIS_STATE_MOTOR_CALIBRATION = 4
AXIS_STATE_SENSORLESS_CONTROL = 5
AXIS_STATE_ENCODER_INDEX_SEARCH = 6
AXIS_STATE_ENCODER_OFFSET_CALIBRATION = 7
AXIS_STATE_CLOSED_LOOP_CONTROL = 8
AXIS_STATE_LOCKIN_SPIN = 9
AXIS_STATE_ENCODER_DIR_FIND = 10
AXIS_STATE_HOMING = 11
# ODrive.Can.Protocol
PROTOCOL_SIMPLE = 0
class errors:
class axis:
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
ERROR_WATCHDOG_TIMER_EXPIRED = 0x800
ERROR_MIN_ENDSTOP_PRESSED = 0x1000
ERROR_MAX_ENDSTOP_PRESSED = 0x2000
ERROR_ESTOP_REQUESTED = 0x4000
ERROR_HOMING_WITHOUT_ENDSTOP = 0x20000
# ODrive.Axis.AxisState
AXIS_STATE_UNDEFINED = 0
AXIS_STATE_IDLE = 1
AXIS_STATE_STARTUP_SEQUENCE = 2
AXIS_STATE_FULL_CALIBRATION_SEQUENCE = 3
AXIS_STATE_MOTOR_CALIBRATION = 4
AXIS_STATE_SENSORLESS_CONTROL = 5
AXIS_STATE_ENCODER_INDEX_SEARCH = 6
AXIS_STATE_ENCODER_OFFSET_CALIBRATION = 7
AXIS_STATE_CLOSED_LOOP_CONTROL = 8
AXIS_STATE_LOCKIN_SPIN = 9
AXIS_STATE_ENCODER_DIR_FIND = 10
AXIS_STATE_HOMING = 11
class motor:
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_CURRENT_LIMIT_VIOLATION = 0x1000
ERROR_BRAKE_DUTY_CYCLE_NAN = 0x2000
ERROR_DC_BUS_OVER_REGEN_CURRENT = 0x4000
ERROR_DC_BUS_OVER_CURRENT = 0x8000
# ODrive.Encoder.Mode
ENCODER_MODE_INCREMENTAL = 0
ENCODER_MODE_HALL = 1
ENCODER_MODE_SINCOS = 2
ENCODER_MODE_SPI_ABS_CUI = 256
ENCODER_MODE_SPI_ABS_AMS = 257
ENCODER_MODE_SPI_ABS_AEAT = 258
class encoder:
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
# ODrive.Controller.ControlMode
CONTROL_MODE_VOLTAGE_CONTROL = 0
CONTROL_MODE_CURRENT_CONTROL = 1
CONTROL_MODE_VELOCITY_CONTROL = 2
CONTROL_MODE_POSITION_CONTROL = 3
class controller:
ERROR_NONE = 0
ERROR_OVERSPEED = 0x01
ERROR_INVALID_INPUT_MODE = 0x02
ERROR_UNSTABLE_GAIN = 0x04
ERROR_INVALID_MIRROR_AXIS = 0x08
# ODrive.Controller.InputMode
INPUT_MODE_INACTIVE = 0
INPUT_MODE_PASSTHROUGH = 1
INPUT_MODE_VEL_RAMP = 2
INPUT_MODE_POS_FILTER = 3
INPUT_MODE_MIX_CHANNELS = 4
INPUT_MODE_TRAP_TRAJ = 5
INPUT_MODE_CURRENT_RAMP = 6
INPUT_MODE_MIRROR = 7
MOTOR_TYPE_HIGH_CURRENT = 0
#MOTOR_TYPE_LOW_CURRENT = 1
MOTOR_TYPE_GIMBAL = 2
# ODrive.Motor.MotorType
MOTOR_TYPE_HIGH_CURRENT = 0
MOTOR_TYPE_GIMBAL = 2
MOTOR_TYPE_ACIM = 3
CTRL_MODE_VOLTAGE_CONTROL = 0
CTRL_MODE_CURRENT_CONTROL = 1
CTRL_MODE_VELOCITY_CONTROL = 2
CTRL_MODE_POSITION_CONTROL = 3
# ODrive.Can.Error
CAN_ERROR_NONE = 0x00000000
CAN_ERROR_DUPLICATE_CAN_IDS = 0x00000001
INPUT_MODE_INACTIVE = 0
INPUT_MODE_PASSTHROUGH = 1
INPUT_MODE_VEL_RAMP = 2
INPUT_MODE_POS_FILTER = 3
INPUT_MODE_MIX_CHANNELS = 4
INPUT_MODE_TRAP_TRAJ = 5
INPUT_MODE_CURRENT_RAMP = 6
INPUT_MODE_MIRROR = 7
# ODrive.Axis.Error
AXIS_ERROR_NONE = 0x00000000
AXIS_ERROR_INVALID_STATE = 0x00000001
AXIS_ERROR_DC_BUS_UNDER_VOLTAGE = 0x00000002
AXIS_ERROR_DC_BUS_OVER_VOLTAGE = 0x00000004
AXIS_ERROR_CURRENT_MEASUREMENT_TIMEOUT = 0x00000008
AXIS_ERROR_BRAKE_RESISTOR_DISARMED = 0x00000010
AXIS_ERROR_MOTOR_DISARMED = 0x00000020
AXIS_ERROR_MOTOR_FAILED = 0x00000040
AXIS_ERROR_SENSORLESS_ESTIMATOR_FAILED = 0x00000080
AXIS_ERROR_ENCODER_FAILED = 0x00000100
AXIS_ERROR_CONTROLLER_FAILED = 0x00000200
AXIS_ERROR_POS_CTRL_DURING_SENSORLESS = 0x00000400
AXIS_ERROR_WATCHDOG_TIMER_EXPIRED = 0x00000800
AXIS_ERROR_MIN_ENDSTOP_PRESSED = 0x00001000
AXIS_ERROR_MAX_ENDSTOP_PRESSED = 0x00002000
AXIS_ERROR_ESTOP_REQUESTED = 0x00004000
AXIS_ERROR_HOMING_WITHOUT_ENDSTOP = 0x00020000
ENCODER_MODE_INCREMENTAL = 0x00
ENCODER_MODE_HALL = 0x01
ENCODER_MODE_SINCOS = 0x02
ENCODER_MODE_SPI_ABS_CUI = 0x100
ENCODER_MODE_SPI_ABS_AMS = 0x101
ENCODER_MODE_SPI_ABS_AEAT = 0x102
# ODrive.Axis.LockinState
LOCKIN_STATE_INACTIVE = 0
LOCKIN_STATE_RAMP = 1
LOCKIN_STATE_ACCELERATE = 2
LOCKIN_STATE_CONST_VEL = 3
# ODrive.Motor.Error
MOTOR_ERROR_NONE = 0x00000000
MOTOR_ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x00000001
MOTOR_ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x00000002
MOTOR_ERROR_ADC_FAILED = 0x00000004
MOTOR_ERROR_DRV_FAULT = 0x00000008
MOTOR_ERROR_CONTROL_DEADLINE_MISSED = 0x00000010
MOTOR_ERROR_NOT_IMPLEMENTED_MOTOR_TYPE = 0x00000020
MOTOR_ERROR_BRAKE_CURRENT_OUT_OF_RANGE = 0x00000040
MOTOR_ERROR_MODULATION_MAGNITUDE = 0x00000080
MOTOR_ERROR_BRAKE_DEADTIME_VIOLATION = 0x00000100
MOTOR_ERROR_UNEXPECTED_TIMER_CALLBACK = 0x00000200
MOTOR_ERROR_CURRENT_SENSE_SATURATION = 0x00000400
MOTOR_ERROR_INVERTER_OVER_TEMP = 0x00000800
MOTOR_ERROR_CURRENT_LIMIT_VIOLATION = 0x00001000
MOTOR_ERROR_BRAKE_DUTY_CYCLE_NAN = 0x00002000
MOTOR_ERROR_DC_BUS_OVER_REGEN_CURRENT = 0x00004000
MOTOR_ERROR_DC_BUS_OVER_CURRENT = 0x00008000
# ODrive.Motor.ArmedState
ARMED_STATE_DISARMED = 0
ARMED_STATE_WAITING_FOR_TIMINGS = 1
ARMED_STATE_WAITING_FOR_UPDATE = 2
ARMED_STATE_ARMED = 3
# ODrive.Motor.GateDriver.DrvFault
DRV_FAULT_NO_FAULT = 0x00000000
DRV_FAULT_FET_LOW_C_OVERCURRENT = 0x00000001
DRV_FAULT_FET_HIGH_C_OVERCURRENT = 0x00000002
DRV_FAULT_FET_LOW_B_OVERCURRENT = 0x00000004
DRV_FAULT_FET_HIGH_B_OVERCURRENT = 0x00000008
DRV_FAULT_FET_LOW_A_OVERCURRENT = 0x00000010
DRV_FAULT_FET_HIGH_A_OVERCURRENT = 0x00000020
DRV_FAULT_OVERTEMPERATURE_WARNING = 0x00000040
DRV_FAULT_OVERTEMPERATURE_SHUTDOWN = 0x00000080
DRV_FAULT_P_VDD_UNDERVOLTAGE = 0x00000100
DRV_FAULT_G_VDD_UNDERVOLTAGE = 0x00000200
DRV_FAULT_G_VDD_OVERVOLTAGE = 0x00000400
# ODrive.Controller.Error
CONTROLLER_ERROR_NONE = 0x00000000
CONTROLLER_ERROR_OVERSPEED = 0x00000001
CONTROLLER_ERROR_INVALID_INPUT_MODE = 0x00000002
CONTROLLER_ERROR_UNSTABLE_GAIN = 0x00000004
CONTROLLER_ERROR_INVALID_MIRROR_AXIS = 0x00000008
CONTROLLER_ERROR_INVALID_LOAD_ENCODER = 0x00000010
CONTROLLER_ERROR_INVALID_ESTIMATE = 0x00000020
# ODrive.Encoder.Error
ENCODER_ERROR_NONE = 0x00000000
ENCODER_ERROR_UNSTABLE_GAIN = 0x00000001
ENCODER_ERROR_CPR_POLEPAIRS_MISMATCH = 0x00000002
ENCODER_ERROR_NO_RESPONSE = 0x00000004
ENCODER_ERROR_UNSUPPORTED_ENCODER_MODE = 0x00000008
ENCODER_ERROR_ILLEGAL_HALL_STATE = 0x00000010
ENCODER_ERROR_INDEX_NOT_FOUND_YET = 0x00000020
ENCODER_ERROR_ABS_SPI_TIMEOUT = 0x00000040
ENCODER_ERROR_ABS_SPI_COM_FAIL = 0x00000080
ENCODER_ERROR_ABS_SPI_NOT_READY = 0x00000100
# ODrive.SensorlessEstimator.Error
SENSORLESS_ESTIMATOR_ERROR_NONE = 0x00000000
SENSORLESS_ESTIMATOR_ERROR_UNSTABLE_GAIN = 0x00000001

Some files were not shown because too many files have changed in this diff Show More