From 1899fa7df5b98faf794182e300fb57b066d05139 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 13 Oct 2020 22:01:52 -0400 Subject: [PATCH 01/14] Moved thermistors from belonging to Axis to being a part of Motor. Thermistor apply_config now called in Motor::apply_config() Thermistor errors rolled into motor errors odrivetool and GUI updated to handle change Removed CurrentLimiter,Thermistor arrays abstraction --- Firmware/Board/v3/board.cpp | 15 +++++++------- Firmware/MotorControl/axis.cpp | 22 +++----------------- Firmware/MotorControl/axis.hpp | 9 --------- Firmware/MotorControl/main.cpp | 12 +++++------ Firmware/MotorControl/motor.cpp | 30 ++++++++++++++++++++-------- Firmware/MotorControl/motor.hpp | 6 +++++- Firmware/MotorControl/thermistor.cpp | 11 ++++++---- Firmware/MotorControl/thermistor.hpp | 7 ++++--- Firmware/odrive-interface.yaml | 16 +++++---------- GUI/src/assets/odriveEnums.json | 5 ++--- GUI/src/components/Axis.vue | 23 +++++++-------------- docs/thermistors.md | 6 +++--- tools/odrive/enums.py | 6 ++---- tools/odrive/utils.py | 10 ++++------ 14 files changed, 78 insertions(+), 100 deletions(-) diff --git a/Firmware/Board/v3/board.cpp b/Firmware/Board/v3/board.cpp index d2b0afbb..716329cf 100644 --- a/Firmware/Board/v3/board.cpp +++ b/Firmware/Board/v3/board.cpp @@ -58,20 +58,26 @@ OnboardThermistorCurrentLimiter fet_thermistors[AXIS_COUNT] = { } }; +OffboardThermistorCurrentLimiter motor_thermistors[AXIS_COUNT]; + Motor motors[AXIS_COUNT] = { { &htim1, // timer TIM_1_8_PERIOD_CLOCKS, // control_deadline 1.0f / SHUNT_RESISTANCE, // shunt_conductance [S] m0_gate_driver, // gate_driver - m0_gate_driver // opamp + m0_gate_driver, // opamp + fet_thermistors[0], + motor_thermistors[0] }, { &htim8, // timer (3 * TIM_1_8_PERIOD_CLOCKS) / 2, // control_deadline 1.0f / SHUNT_RESISTANCE, // shunt_conductance [S] m1_gate_driver, // gate_driver - m1_gate_driver // opamp + m1_gate_driver, // opamp + fet_thermistors[1], + motor_thermistors[1] } }; @@ -101,7 +107,6 @@ MechanicalBrake mechanical_brakes[AXIS_COUNT]; SensorlessEstimator sensorless_estimators[AXIS_COUNT]; Controller controllers[AXIS_COUNT]; TrapezoidalTrajectory trap[AXIS_COUNT]; -OffboardThermistorCurrentLimiter motor_thermistors[AXIS_COUNT]; std::array axes{{ { @@ -112,8 +117,6 @@ std::array axes{{ encoders[0], // encoder sensorless_estimators[0], // sensorless_estimator controllers[0], // controller - fet_thermistors[0], // fet_thermistor - motor_thermistors[0], // motor_thermistor motors[0], // motor trap[0], // trap endstops[0], endstops[1], // min_endstop, max_endstop @@ -132,8 +135,6 @@ std::array axes{{ encoders[1], // encoder sensorless_estimators[1], // sensorless_estimator controllers[1], // controller - fet_thermistors[1], // fet_thermistor - motor_thermistors[1], // motor_thermistor motors[1], // motor trap[1], // trap endstops[2], endstops[3], // min_endstop, max_endstop diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 52150800..f2ffb138 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -14,8 +14,6 @@ Axis::Axis(int axis_num, Encoder& encoder, SensorlessEstimator& sensorless_estimator, Controller& controller, - OnboardThermistorCurrentLimiter& fet_thermistor, - OffboardThermistorCurrentLimiter& motor_thermistor, Motor& motor, TrapezoidalTrajectory& trap, Endstop& min_endstop, @@ -28,25 +26,15 @@ Axis::Axis(int axis_num, encoder_(encoder), sensorless_estimator_(sensorless_estimator), controller_(controller), - fet_thermistor_(fet_thermistor), - motor_thermistor_(motor_thermistor), motor_(motor), trap_traj_(trap), min_endstop_(min_endstop), max_endstop_(max_endstop), - mechanical_brake_(mechanical_brake), - current_limiters_(make_array( - static_cast(&fet_thermistor), - static_cast(&motor_thermistor))), - thermistors_(make_array( - static_cast(&fet_thermistor), - static_cast(&motor_thermistor))) + mechanical_brake_(mechanical_brake) { encoder_.axis_ = this; sensorless_estimator_.axis_ = this; controller_.axis_ = this; - fet_thermistor_.axis_ = this; - motor_thermistor.axis_ = this; motor_.axis_ = this; trap_traj_.axis_ = this; min_endstop_.axis_ = this; @@ -180,9 +168,6 @@ bool Axis::do_checks() { // Sub-components should use set_error which will propegate to this error_ motor_.effective_current_lim(); - for (ThermistorCurrentLimiter* thermistor : thermistors_) { - thermistor->do_checks(); - } motor_.do_checks(); // encoder_.do_checks(); // sensorless_estimator_.do_checks(); @@ -201,11 +186,10 @@ bool Axis::do_checks() { // @brief Update all esitmators bool Axis::do_updates() { // Sub-components should use set_error which will propegate to this error_ - for (ThermistorCurrentLimiter* thermistor : thermistors_) { - thermistor->update(); - } encoder_.update(); sensorless_estimator_.update(); + motor_.fet_thermistor_.update(); + motor_.motor_thermistor_.update(); min_endstop_.update(); max_endstop_.update(); bool ret = check_for_errors(); diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index b3965b39..92964a5f 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -86,8 +86,6 @@ public: Encoder& encoder, SensorlessEstimator& sensorless_estimator, Controller& controller, - OnboardThermistorCurrentLimiter& fet_thermistor, - OffboardThermistorCurrentLimiter& motor_thermistor, Motor& motor, TrapezoidalTrajectory& trap, Endstop& min_endstop, @@ -216,19 +214,12 @@ public: Encoder& encoder_; SensorlessEstimator& sensorless_estimator_; Controller& controller_; - OnboardThermistorCurrentLimiter& fet_thermistor_; - OffboardThermistorCurrentLimiter& motor_thermistor_; Motor& motor_; TrapezoidalTrajectory& trap_traj_; Endstop& min_endstop_; Endstop& max_endstop_; MechanicalBrake& mechanical_brake_; - // List of current_limiters and thermistors to - // provide easy iteration. - std::array current_limiters_; - std::array thermistors_; - osThreadId thread_id_; const uint32_t stack_size_ = 2048; // Bytes volatile bool thread_id_valid_ = false; diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 27d7be86..54fe1fa4 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -50,8 +50,8 @@ static bool config_read_all() { config_manager.read(&axes[i].max_endstop_.config_) && config_manager.read(&axes[i].mechanical_brake_.config_) && config_manager.read(&motors[i].config_) && - config_manager.read(&fet_thermistors[i].config_) && - config_manager.read(&axes[i].motor_thermistor_.config_) && + config_manager.read(&motors[i].fet_thermistor_.config_) && + config_manager.read(&motors[i].motor_thermistor_.config_) && config_manager.read(&axes[i].config_); } return success; @@ -70,8 +70,8 @@ static bool config_write_all() { config_manager.write(&axes[i].max_endstop_.config_) && config_manager.write(&axes[i].mechanical_brake_.config_) && config_manager.write(&motors[i].config_) && - config_manager.write(&fet_thermistors[i].config_) && - config_manager.write(&axes[i].motor_thermistor_.config_) && + config_manager.write(&motors[i].fet_thermistor_.config_) && + config_manager.write(&motors[i].motor_thermistor_.config_) && config_manager.write(&axes[i].config_); } return success; @@ -90,8 +90,8 @@ static void config_clear_all() { axes[i].max_endstop_.config_ = {}; axes[i].mechanical_brake_.config_ = {}; motors[i].config_ = {}; - fet_thermistors[i].config_ = {}; - axes[i].motor_thermistor_.config_ = {}; + motors[i].fet_thermistor_.config_ = {}; + motors[i].motor_thermistor_.config_ = {}; axes[i].clear_config(); } } diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 355067f5..e490828b 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -10,12 +10,16 @@ Motor::Motor(TIM_HandleTypeDef* timer, uint16_t control_deadline, float shunt_conductance, TGateDriver& gate_driver, - TOpAmp& opamp) : + TOpAmp& opamp, + OnboardThermistorCurrentLimiter& fet_thermistor, + OffboardThermistorCurrentLimiter& motor_thermistor) : timer_(timer), control_deadline_(control_deadline), shunt_conductance_(shunt_conductance), gate_driver_(gate_driver), - opamp_(opamp) { + opamp_(opamp), + fet_thermistor_(fet_thermistor), + motor_thermistor_(motor_thermistor) { apply_config(); } @@ -66,6 +70,9 @@ bool Motor::apply_config() { config_.parent = this; is_calibrated_ = config_.pre_calibrated; update_current_controller_gains(); + fet_thermistor_.motor_ = this; + motor_thermistor_.motor_ = this; + motor_thermistor_.apply_config(); return true; } @@ -110,7 +117,16 @@ bool Motor::do_checks() { set_error(ERROR_DRV_FAULT); return false; } - + if (!motor_thermistor_.do_checks()) { + axis_->error_ |= Axis::ERROR_OVER_TEMP; + set_error(ERROR_MOTOR_THERMISTOR_OVER_TEMP); + return false; + } + if (!fet_thermistor_.do_checks()) { + axis_->error_ |= Axis::ERROR_OVER_TEMP; + set_error(ERROR_FET_THERMISTOR_OVER_TEMP); + return false; + } return true; } @@ -124,11 +140,9 @@ float Motor::effective_current_lim() { current_lim = std::min(current_lim, axis_->motor_.current_control_.max_allowed_current); } - // Apply axis current limiters - for (const CurrentLimiter* const limiter : axis_->current_limiters_) { - current_lim = std::min(current_lim, limiter->get_current_limit(config_.current_lim)); - } - + // Apply thermistor current limiters + current_lim = std::min(current_lim, motor_thermistor_.get_current_limit(config_.current_lim)); + current_lim = std::min(current_lim, fet_thermistor_.get_current_limit(config_.current_lim)); effective_current_lim_ = current_lim; return effective_current_lim_; diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 3f61725a..088b300a 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -99,7 +99,9 @@ public: uint16_t control_deadline, float shunt_conductance, TGateDriver& gate_driver, - TOpAmp& opamp); + TOpAmp& opamp, + OnboardThermistorCurrentLimiter& fet_thermistor, + OffboardThermistorCurrentLimiter& motor_thermistor); bool arm(); void disarm(); @@ -130,6 +132,8 @@ public: const float shunt_conductance_; TGateDriver& gate_driver_; TOpAmp& opamp_; + OnboardThermistorCurrentLimiter& fet_thermistor_; + OffboardThermistorCurrentLimiter& motor_thermistor_; Config_t config_; Axis* axis_ = nullptr; // set by Axis constructor diff --git a/Firmware/MotorControl/thermistor.cpp b/Firmware/MotorControl/thermistor.cpp index 3baf78a0..0b1f9704 100644 --- a/Firmware/MotorControl/thermistor.cpp +++ b/Firmware/MotorControl/thermistor.cpp @@ -14,8 +14,7 @@ ThermistorCurrentLimiter::ThermistorCurrentLimiter(uint16_t adc_channel, temperature_(NAN), temp_limit_lower_(temp_limit_lower), temp_limit_upper_(temp_limit_upper), - enabled_(enabled), - error_(ERROR_NONE) + enabled_(enabled) { } @@ -27,8 +26,6 @@ void ThermistorCurrentLimiter::update() { bool ThermistorCurrentLimiter::do_checks() { if (enabled_ && temperature_ >= temp_limit_upper_ + 5) { - error_ = ERROR_OVER_TEMP; - axis_->error_ |= Axis::ERROR_OVER_TEMP; return false; } return true; @@ -70,6 +67,12 @@ OffboardThermistorCurrentLimiter::OffboardThermistorCurrentLimiter() : decode_pin(); } +bool OffboardThermistorCurrentLimiter::apply_config() { + config_.parent = this; + decode_pin(); + return true; +} + void OffboardThermistorCurrentLimiter::decode_pin() { adc_channel_ = channel_from_gpio(get_gpio(config_.gpio_pin)); } diff --git a/Firmware/MotorControl/thermistor.hpp b/Firmware/MotorControl/thermistor.hpp index a7c29cfe..757a40fa 100644 --- a/Firmware/MotorControl/thermistor.hpp +++ b/Firmware/MotorControl/thermistor.hpp @@ -1,7 +1,7 @@ #ifndef __THERMISTOR_HPP #define __THERMISTOR_HPP -class Axis; // declared in axis.hpp +class Motor; // declared in motor.hpp #include "current_limiter.hpp" #include @@ -28,8 +28,7 @@ public: const float& temp_limit_lower_; const float& temp_limit_upper_; const bool& enabled_; - Error error_; - Axis* axis_ = nullptr; // set by Axis constructor + Motor* motor_ = nullptr; // set by Motor::apply_config() }; class OnboardThermistorCurrentLimiter : public ThermistorCurrentLimiter, public ODriveIntf::OnboardThermistorCurrentLimiterIntf { @@ -68,6 +67,8 @@ public: Config_t config_; + bool apply_config(); + private: void decode_pin(); }; diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 8d451ab5..c9afb7a3 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -355,7 +355,7 @@ interfaces: bit: 17 doc: the min endstop was not enabled during homing OverTemp: - doc: Check `fet_thermistor.error` and `motor_thermistor.error` for more information. + doc: Check `motor.error` for more details. step_dir_active: readonly bool current_state: readonly AxisState requested_state: AxisState @@ -444,8 +444,6 @@ interfaces: # status_reg_2: readonly uint32 # ctrl_reg_1: readonly uint32 # ctrl_reg_2: readonly uint32 - fet_thermistor: OnboardThermistorCurrentLimiter - motor_thermistor: OffboardThermistorCurrentLimiter motor: Motor controller: Controller encoder: Encoder @@ -491,7 +489,6 @@ interfaces: ODrive.OnboardThermistorCurrentLimiter: c_is_class: True attributes: - error: ThermistorCurrentLimiter.Error temperature: readonly float32 config: c_is_class: False @@ -507,7 +504,6 @@ interfaces: ODrive.OffboardThermistorCurrentLimiter: c_is_class: True attributes: - error: ThermistorCurrentLimiter.Error temperature: readonly float32 config: c_is_class: False @@ -605,6 +601,8 @@ interfaces: DcBusOverRegenCurrent: {doc: too much current pushed into the power supply} DcBusOverCurrent: {doc: too much current pulled out of the power supply} ModulationIsNan: + MotorThermistorOverTemp: {doc: The motor thermistor measured a temperature above motor.motor_thermistor.config.temp_limit_upper} + FetThermistorOverTemp: {doc: The inverter thermistor measured a temperature above motor.fet_thermistor.config.temp_limit_upper} armed_state: typeargs: {fibre.Property.mode: readonly} values: @@ -619,6 +617,8 @@ interfaces: DC_calib_phC: {type: float32, c_name: DC_calib_.phC} phase_current_rev_gain: float32 effective_current_lim: readonly float32 + fet_thermistor: OnboardThermistorCurrentLimiter + motor_thermistor: OffboardThermistorCurrentLimiter current_control: c_is_class: False attributes: @@ -1020,12 +1020,6 @@ valuetypes: doc: Endstops must be enabled to use this feature. - ODrive.ThermistorCurrentLimiter.Error: - nullflag: None - flags: - OverTemp: - doc: The thermistor temperature upper limit was exceeded. - ODrive.Encoder.Mode: values: Incremental: diff --git a/GUI/src/assets/odriveEnums.json b/GUI/src/assets/odriveEnums.json index 3bc38b81..02dbc47a 100644 --- a/GUI/src/assets/odriveEnums.json +++ b/GUI/src/assets/odriveEnums.json @@ -29,9 +29,6 @@ "AXIS_STATE_ENCODER_DIR_FIND" : 10, "AXIS_STATE_HOMING" : 11, -"THERMISTOR_CURRENT_LIMITER_ERROR_NONE" : 0, -"THERMISTOR_CURRENT_LIMITER_ERROR_OVER_TEMP" : 1, - "ENCODER_MODE_INCREMENTAL" : 0, "ENCODER_MODE_HALL" : 1, "ENCODER_MODE_SINCOS" : 2, @@ -102,6 +99,8 @@ "MOTOR_ERROR_DC_BUS_OVER_REGEN_CURRENT" : 16384, "MOTOR_ERROR_DC_BUS_OVER_CURRENT" : 32768, "MOTOR_ERROR_MODULATION_IS_NAN" : 65536, +"MOTOR_ERROR_MOTOR_THERMISTOR_OVER_TEMP" : 131072, +"MOTOR_ERROR_FET_THERMISTOR_OVER_TEMP" : 262144, "ARMED_STATE_DISARMED" : 0, "ARMED_STATE_WAITING_FOR_TIMINGS" : 1, diff --git a/GUI/src/components/Axis.vue b/GUI/src/components/Axis.vue index 0bd9f87d..2ee3503a 100644 --- a/GUI/src/components/Axis.vue +++ b/GUI/src/components/Axis.vue @@ -66,6 +66,9 @@ const motorErrors = { 0x00002000: "MOTOR_ERROR_BRAKE_DUTY_CYCLE_NAN", 0x00004000: "MOTOR_ERROR_DC_BUS_OVER_REGEN_CURRENT", 0x00008000: "MOTOR_ERROR_DC_BUS_OVER_CURRENT", + 0x00010000: "MOTOR_ERROR_MODULATION_IS_NAN", + 0x00020000: "MOTOR_ERROR_MOTOR_THERMISTOR_OVER_TEMP", + 0x00040000: "MOTOR_ERROR_FET_THERMISTOR_OVER_TEMP", }; let encoderErrors = { @@ -125,10 +128,7 @@ export default { errs.push(axisErrors[errKey]); } } - retMsg = ""; - for (const err of errs) { - retMsg = retMsg + " " + err; - } + retMsg = errs.join(', '); } return retMsg; @@ -150,10 +150,7 @@ export default { errs.push(motorErrors[errKey]); } } - retMsg = ""; - for (const err of errs) { - retMsg = retMsg + " " + err; - } + retMsg = errs.join(', '); } return retMsg; @@ -175,10 +172,7 @@ export default { errs.push(encoderErrors[errKey]); } } - retMsg = ""; - for (const err of errs) { - retMsg = retMsg + " " + err; - } + retMsg = errs.join(', '); } return retMsg; @@ -195,10 +189,7 @@ export default { errs.push(controllerErrors[errKey]); } } - retMsg = ""; - for (const err of errs) { - retMsg = retMsg + " " + err; - } + retMsg = errs.join(', '); } return retMsg; diff --git a/docs/thermistors.md b/docs/thermistors.md index ec13679e..144734fe 100644 --- a/docs/thermistors.md +++ b/docs/thermistors.md @@ -4,11 +4,11 @@ Thermistors are elements that change their resistance based on the temperature. They can be used to electrically measure temperature. The ODrive itself has thermistors on board near the FETs to ensure that they don't burn themselves out. In addition to this it's possible to connect your own thermistor to measure the temperature of the connected motors. There are two types of thermistors, Negative Temperature Coefficient (NTC) and Positive Temperature Coefficient (PTC). This indicates whether the resistance goes up or down when the temperature goes up or down. The ODrive only supports the NTC type thermistor. ## FET thermistor -The temperature of the onboard FET thermistors can be read out by using the `odrivetool` under `.fet_thermistor.temp`. The odrive will automatically start current limiting the motor when the `.fet_thermistor.config.temp_limit_lower` threshold is exceeded and once `.fet_thermistor.config.temp_limit_upper` is exceeded the ODrive will stop controlling the motor and set an error. The lower and upper threshold can be changed, but this is not recommended. +The temperature of the onboard FET thermistors can be read out by using the `odrivetool` under `.motor.fet_thermistor.temperature`. The odrive will automatically start current limiting the motor when the `.motor.fet_thermistor.config.temp_limit_lower` threshold is exceeded and once `.motor.fet_thermistor.config.temp_limit_upper` is exceeded the ODrive will stop controlling the motor and set an error. The lower and upper threshold can be changed, but this is not recommended. ## Connecting motor thermistors -To use your own thermistors with the ODrive a few things have to be clarified first. The use of your own thermistor requires one analog input pin. Under `.motor_thermistor.config` the configuration of your own thermistor is available with the following fields: +To use your own thermistors with the ODrive a few things have to be clarified first. The use of your own thermistor requires one analog input pin. Under `.motor.motor_thermistor.config` the configuration of your own thermistor is available with the following fields: * `gpio_pin`: The GPIO input in used for this thermistor. * `poly_coefficient_0` to `poly_coefficient_3`: Coefficient that needs to be set for your specific setup more on that in [Thermistor coefficients](#thermistor-coefficients). @@ -25,7 +25,7 @@ The way this works is that the thermistor is connected in series with a known re To use a thermistor with the ODrive a voltage divider circuit has to be made that uses `VCCA` as the power source with `GNDA` as the ground. The voltage divider output can be connected to a GPIO pin that supports analog input. ## Thermistor coefficients -Every thermistor and voltage divider circuit is different and thus it's necessary to let the ODrive know how to relate a voltage it measures at the GPIO pin to a temperature. The `poly_coefficient_0` to `poly_coefficient_3` under `.motor_thermistor.config` are used for this. The `odrivetool` has a convenience function `set_motor_thermistor_coeffs(axis, Rload, R_25, Beta, Tmin, Tmax)` which can be used to calculate and set these coefficients. +Every thermistor and voltage divider circuit is different and thus it's necessary to let the ODrive know how to relate a voltage it measures at the GPIO pin to a temperature. The `poly_coefficient_0` to `poly_coefficient_3` under `.motor.motor_thermistor.config` are used for this. The `odrivetool` has a convenience function `set_motor_thermistor_coeffs(axis, Rload, R_25, Beta, Tmin, Tmax)` which can be used to calculate and set these coefficients. * `axis`: Which axis do set the motor thermistor coefficients for (`odrv0.axis0` or `odrv0.axis1`). * `Rload`: The Ohm value of the resistor used in the voltage divider circuit. diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index ea11aa8e..0957576a 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -37,10 +37,6 @@ AXIS_STATE_LOCKIN_SPIN = 9 AXIS_STATE_ENCODER_DIR_FIND = 10 AXIS_STATE_HOMING = 11 -# ODrive.ThermistorCurrentLimiter.Error -THERMISTOR_CURRENT_LIMITER_ERROR_NONE = 0x00000000 -THERMISTOR_CURRENT_LIMITER_ERROR_OVER_TEMP = 0x00000001 - # ODrive.Encoder.Mode ENCODER_MODE_INCREMENTAL = 0 ENCODER_MODE_HALL = 1 @@ -119,6 +115,8 @@ MOTOR_ERROR_BRAKE_DUTY_CYCLE_NAN = 0x00002000 MOTOR_ERROR_DC_BUS_OVER_REGEN_CURRENT = 0x00004000 MOTOR_ERROR_DC_BUS_OVER_CURRENT = 0x00008000 MOTOR_ERROR_MODULATION_IS_NAN = 0x00010000 +MOTOR_ERROR_MOTOR_THERMISTOR_OVER_TEMP = 0x00020000 +MOTOR_ERROR_FET_THERMISTOR_OVER_TEMP = 0x00040000 # ODrive.Motor.ArmedState ARMED_STATE_DISARMED = 0 diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 329cda81..c3a15742 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -64,10 +64,10 @@ class OperationAbortedException(Exception): def set_motor_thermistor_coeffs(axis, Rload, R_25, Beta, Tmin, TMax): coeffs = calculate_thermistor_coeffs(3, Rload, R_25, Beta, Tmin, TMax) - axis.motor_thermistor.config.poly_coefficient_0 = float(coeffs[3]) - axis.motor_thermistor.config.poly_coefficient_1 = float(coeffs[2]) - axis.motor_thermistor.config.poly_coefficient_2 = float(coeffs[1]) - axis.motor_thermistor.config.poly_coefficient_3 = float(coeffs[0]) + axis.motor.motor_thermistor.config.poly_coefficient_0 = float(coeffs[3]) + axis.motor.motor_thermistor.config.poly_coefficient_1 = float(coeffs[2]) + axis.motor.motor_thermistor.config.poly_coefficient_2 = float(coeffs[1]) + axis.motor.motor_thermistor.config.poly_coefficient_3 = float(coeffs[0]) def dump_errors(odrv, clear=False): axes = [(name, axis) for name, axis in odrv._remote_attributes.items() if 'axis' in name] @@ -80,8 +80,6 @@ def dump_errors(odrv, clear=False): module_decode_map = [ (name, odrv, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("AXIS_ERROR_")}), ('motor', axis, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("MOTOR_ERROR_")}), - ('fet_thermistor', axis, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("THERMISTOR_CURRENT_LIMITER_ERROR")}), - ('motor_thermistor', axis, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("THERMISTOR_CURRENT_LIMITER_ERROR")}), ('encoder', axis, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("ENCODER_ERROR_")}), ('controller', axis, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("CONTROLLER_ERROR_")}), ] From 250831b769f9b35a81dd63003a663a447e959553 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 13 Oct 2020 22:17:18 -0400 Subject: [PATCH 02/14] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4cf5c05..c275bf60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,13 @@ Please add a note of your changes below this heading if you make a Pull Request. ### Changed +* Moved thermistors from being a top level object to belonging to Motor objects. Also changed errors: thermistor errors rolled into motor errors * Use DMA for DRV8301 setup * Make NVM configuration code more dynamic so that the layout doesn't have to be known at compile time. * GPIO initialization logic was changed. GPIOs now need to be explicitly set to the mode corresponding to the feature that they are used by. See `.config.gpioX_mode`. * Previously, if two components used the same interrupt pin (e.g. step input for axis0 and axis1) then the one that was configured later would override the other one. Now this is no longer the case (the old component remains the owner of the pin). -### API Miration Notes +### API Migration Notes * `enable_uart` and `uart_baudrate` were renamed to `enable_uart0` and `uart0_baudrate`. * `enable_i2c_instead_of_can` was replaced by the separate settings `enable_i2c0` and `enable_can0`. From b09eecc05e2a8be885177333c7d5eed496dcfbad Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 16 Oct 2020 13:15:01 -0700 Subject: [PATCH 03/14] Update getting-started.md --- docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 34f93033..7784771c 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -69,7 +69,7 @@ Connect the encoder(s) to J4. The A,B phases are required, and the Z (index puls Always think safety before powering up the ODrive if motors are attached. Consider what might happen if the motor spins as soon as power is applied. * Unlike some devices, the ODrive does not recieve power over the USB port so the 24/56 volt power input is required even just to communicate with it using USB. It is ok to power up the ODrive before or after connecting the USB cable. -* To power up the ODrive, connect the power source to the DC terminals. Make sure to pay attention to the polarity. A small spark is normal. This is caused by the capacitors charging up. +* To power up the ODrive, connect the power source to the DC terminals. Make sure to pay attention to the polarity. Try to connect the power source first and then turn it on to avoid inrush current. If this can't be avoided then a small spark is normal. This is caused by the capacitors charging up. ## Downloading and Installing Tools Most instructions in this guide refer to a utility called `odrivetool`, so you should install that first. From 01174d3597d33d02838f4bcd9370764425e83eed Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 31 Aug 2020 13:03:48 +0200 Subject: [PATCH 04/14] Various DFU fixes - make DFU script compatible with Python 2 - include permissions for DFU mode VID:PID in udev rules (these rules, when missing, would result in `ValueError: The device has no langid`) - fix `IndexError: list index out of range` that occurred when the device was already in DFU mode - fix `UnboundLocalError: local variable 'do_backup_config' referenced before assignment` when DFU was started after the device was already in DFU mode --- Firmware/fibre/python/fibre/discovery.py | 2 +- tools/odrive/dfu.py | 12 ++++++++++-- tools/odrive/utils.py | 3 +++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index bd4ce071..72548ce8 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -176,4 +176,4 @@ def find_any(path="usb", serial_number=None, if find_multiple: return result else: - return result[0] + return result[0] if len(result) > 0 else None diff --git a/tools/odrive/dfu.py b/tools/odrive/dfu.py index 1be62fa6..561ed776 100755 --- a/tools/odrive/dfu.py +++ b/tools/odrive/dfu.py @@ -20,6 +20,13 @@ import odrive from odrive.utils import Event, OperationAbortedException from odrive.dfuse import * +if sys.version_info < (3, 0): + _print = print + def print(*vals, **kwargs): + _print(*vals) + if kwargs.get('flush', False): + sys.stdout.flush() + try: from intelhex import IntelHex except: @@ -142,8 +149,8 @@ class FirmwareFromGithub(Firmware): hw_version_regex = r'.*v([0-9]+).([0-9]+)(-(?P[0-9]+)V)?.hex' hw_version_match = re.search(hw_version_regex, asset_json['name']) - self.hw_version = (int(hw_version_match[1]), - int(hw_version_match[2]), + self.hw_version = (int(hw_version_match.group(1)), + int(hw_version_match.group(2)), int(hw_version_match.groupdict().get('voltage') or 0)) self.github_asset_id = asset_json['id'] self.hex = None @@ -340,6 +347,7 @@ def update_device(device, firmware, logger, cancellation_token): logger.debug(" {:08X} to {:08X}".format(start, end - 1)) # Back up configuration + do_backup_config = False if dfudev is None: do_backup_config = device.user_config_loaded if hasattr(device, 'user_config_loaded') else False if do_backup_config: diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index dd51f5ac..f5699991 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -23,6 +23,9 @@ except ImportError: sys.stdout.flush() pass +if sys.version_info < (3, 0): + input = raw_input + _VT100Colors = { 'green': '\x1b[92;1m', 'cyan': '\x1b[96;1m', From 4d59959e07456f7aecefa3a511477f7473943e2f Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 20 Oct 2020 23:51:29 +0200 Subject: [PATCH 05/14] Continue connecting if JSON caching fails If the first run of a recent version of odrivetool happens with sudo then the cache directory will be owned by root and thus subsequent non-sudo runs of odrivetool fail to create JSON files in the cache directory. Previously odrivetool would silently fail to connect in this case. This commit instead makes odrivetool print a warning and continue to connect. See https://github.com/madcowswe/ODrive/issues/509#issuecomment-713104444 --- Firmware/fibre/python/fibre/discovery.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index 72548ce8..d8781c7c 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -109,10 +109,13 @@ def find_all(path, serial_number, # Save JSON to cache if not cache_path is None: logger.debug("Creating new JSON cache file {}".format(cache_path)) - os.makedirs(cache_dir, exist_ok=True) - with open(cache_path, 'w+') as json_cache: - json_cache.write(json_string) - logger.debug("Saved JSON to cache file {}".format(cache_path)) + try: + os.makedirs(cache_dir, exist_ok=True) + with open(cache_path, 'w+') as json_cache: + json_cache.write(json_string) + logger.debug("Saved JSON to cache file {}".format(cache_path)) + except Exception as ex: + logger.warn("Failed to cache JSON: {}".format(ex)) channel._interface_definition_crc = json_crc16 From 40f9fbb6f58588196cabaad0616ac0b8ce4c05e7 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 31 Aug 2020 13:56:55 +0200 Subject: [PATCH 06/14] add ODrive in DFU mode to udev rules --- tools/odrive/version.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/odrive/version.py b/tools/odrive/version.py index 7a323899..23b38b93 100644 --- a/tools/odrive/version.py +++ b/tools/odrive/version.py @@ -93,6 +93,7 @@ def setup_udev_rules(logger): if logger: logger.warn("you should run this as root, otherwise it will probably not work") with open('/etc/udev/rules.d/91-odrive.rules', 'w') as file: file.write('SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="0d3[0-9]", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1"\n') + file.write('SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="df11", MODE="0666"\n') subprocess.check_call(["udevadm", "control", "--reload-rules"]) subprocess.check_call(["udevadm", "trigger"]) if logger: logger.info('udev rules configured successfully') From fd848f2c362ac8729f09ecd124708e5b1e11326f Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Wed, 21 Oct 2020 03:44:32 -0400 Subject: [PATCH 07/14] Addressed PR comments Updated API Migration Notes Changed Axis error that is thrown for over temp from ERROR_OVER_TEMP to ERROR_MOTOR_FAILED Moved setting of thermistor_.motor_ = this from motor apply_config() to motor constructor Moved motor_thermistor apply_config call from Motor to config_apply_all() in main.cpp --- CHANGELOG.md | 1 + Firmware/MotorControl/main.cpp | 1 + Firmware/MotorControl/motor.cpp | 9 ++++----- Firmware/odrive-interface.yaml | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c275bf60..ba91b673 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Please add a note of your changes below this heading if you make a Pull Request. ### API Migration Notes +* `odrive.axis.fet_thermistor`, `odrive.axis.motor_thermistor` moved to `odrive.axis.motor` object * `enable_uart` and `uart_baudrate` were renamed to `enable_uart0` and `uart0_baudrate`. * `enable_i2c_instead_of_can` was replaced by the separate settings `enable_i2c0` and `enable_can0`. * `.motor.gate_driver` was moved to `.gate_driver`. diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 54fe1fa4..301fe1c4 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -104,6 +104,7 @@ static bool config_apply_all() { && axes[i].min_endstop_.apply_config() && axes[i].max_endstop_.apply_config() && motors[i].apply_config() + && motors[i].motor_thermistor_.apply_config() && axes[i].apply_config(); } return success; diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index e490828b..42ac5ccf 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -21,6 +21,8 @@ Motor::Motor(TIM_HandleTypeDef* timer, fet_thermistor_(fet_thermistor), motor_thermistor_(motor_thermistor) { apply_config(); + fet_thermistor_.motor_ = this; + motor_thermistor_.motor_ = this; } // @brief Arms the PWM outputs that belong to this motor. @@ -70,9 +72,6 @@ bool Motor::apply_config() { config_.parent = this; is_calibrated_ = config_.pre_calibrated; update_current_controller_gains(); - fet_thermistor_.motor_ = this; - motor_thermistor_.motor_ = this; - motor_thermistor_.apply_config(); return true; } @@ -118,12 +117,12 @@ bool Motor::do_checks() { return false; } if (!motor_thermistor_.do_checks()) { - axis_->error_ |= Axis::ERROR_OVER_TEMP; + axis_->error_ |= Axis::ERROR_MOTOR_FAILED; set_error(ERROR_MOTOR_THERMISTOR_OVER_TEMP); return false; } if (!fet_thermistor_.do_checks()) { - axis_->error_ |= Axis::ERROR_OVER_TEMP; + axis_->error_ |= Axis::ERROR_MOTOR_FAILED; set_error(ERROR_FET_THERMISTOR_OVER_TEMP); return false; } diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index c9afb7a3..5494186a 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -355,6 +355,7 @@ interfaces: bit: 17 doc: the min endstop was not enabled during homing OverTemp: + # unused doc: Check `motor.error` for more details. step_dir_active: readonly bool current_state: readonly AxisState From 2d2892f253c00da9dbfcf518450f6f0a7108ce3f Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 21 Oct 2020 11:34:35 +0200 Subject: [PATCH 08/14] reduce likelihood of `IndexError` during DFU In some cases `DfuDevice.control_msg` returns zero bytes. This might be because the device didn't have enough time to process the previous command. This commit adds a single retry after a delay. If the retry fails too, an error message is printed that helps the user to recover the ODrive. Since this issue occurs rarely it's unclear if this commit really solves the issue. --- tools/odrive/dfuse/DfuDevice.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tools/odrive/dfuse/DfuDevice.py b/tools/odrive/dfuse/DfuDevice.py index be7d404e..57035715 100644 --- a/tools/odrive/dfuse/DfuDevice.py +++ b/tools/odrive/dfuse/DfuDevice.py @@ -2,6 +2,7 @@ import usb.util import time import fractions import array +import time from odrive.dfuse.DfuState import DfuState DFU_REQUEST_SEND = 0x21 @@ -64,7 +65,20 @@ class DfuDevice: self.control_msg(DFU_REQUEST_SEND, DFU_CLRSTATUS, 0, None) def get_state(self): - return self.control_msg(DFU_REQUEST_RECEIVE, DFU_GETSTATE, 0, 1)[0] + msg = self.control_msg(DFU_REQUEST_RECEIVE, DFU_GETSTATE, 0, 1) + + # Second chance after giving the device some time to breathe. + if len(msg) == 0: + time.sleep(0.5) + msg = self.control_msg(DFU_REQUEST_RECEIVE, DFU_GETSTATE, 0, 1) + + if len(msg) == 0: + raise Exception("Could not get device state. Firmware upgrade will abort. " + "Please try again. If odrivetool can't find the device " + "anymore after this, follow the instructions in " + "https://docs.odriverobotics.com/odrivetool#device-firmware-update " + "(\"How to force DFU mode\").") + return msg[0] def abort(self): self.control_msg(DFU_REQUEST_RECEIVE, DFU_ABORT, 0, 0) From 3df42396c0933b8dc78db95e28777f4ae4f32ab9 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Fri, 23 Oct 2020 21:11:26 -0400 Subject: [PATCH 09/14] Fixes for the crashes caused by disconnecting one odrive and connecting another while the GUI is open. Fixed numerical inputs to accept "0". --- GUI/server/odrive_server.py | 35 +++++++++++++------ GUI/src/App.vue | 19 ---------- GUI/src/components/Axis.vue | 29 ++++++++++----- GUI/src/components/actions/Action.vue | 2 +- GUI/src/components/controls/CtrlNumeric.vue | 3 +- .../components/wizard/choices/wizardBrake.vue | 2 +- .../choices/wizardEncoderIncremental.vue | 2 +- .../choices/wizardEncoderIncrementalIndex.vue | 2 +- .../components/wizard/choices/wizardMisc.vue | 4 +-- GUI/src/lib/odrive_utils.js | 27 +++++++++----- GUI/src/store.js | 35 ++++++++++++------- 11 files changed, 93 insertions(+), 67 deletions(-) diff --git a/GUI/server/odrive_server.py b/GUI/server/odrive_server.py index 0ee37692..7501d7fa 100644 --- a/GUI/server/odrive_server.py +++ b/GUI/server/odrive_server.py @@ -58,8 +58,12 @@ def discovered_device(device): while globals()['inUse']: time.sleep(0.1) globals()['odrives'][odrive_name] = device + globals()['odrives_status'][odrive_name] = True print("Found " + str(serial_number)) print("odrive list: " + str([key for key in globals()['odrives'].keys()])) + # tell GUI the status of known ODrives (previously connected and then disconnected ODrives will be "False") + socketio.emit('odrives-status', json.dumps(globals()['odrives_status'])) + # triggers a getODrives socketio message socketio.emit('odrive-found') def start_discovery(): @@ -68,9 +72,12 @@ def start_discovery(): shutdown = fibre.Event() fibre.find_all("usb", None, discovered_device, shutdown, shutdown, log) -def handle_disconnect(): +def handle_disconnect(odrive_name): print("lost odrive") - #socketio.emit('odrive-disconnected') + globals()['odrives_status'][odrive_name] = False + # emit the whole list of odrive statuses + # in the GUI, mark and use status as ODrive state. + socketio.emit('odrives-status', json.dumps(globals()['odrives_status'])) @socketio.on('findODrives') def getODrives(message): @@ -115,8 +122,9 @@ def get_odrives(data): odriveDict = {} #for (index, odrv) in enumerate(globals()['odrives']): # odriveDict["odrive" + str(index)] = dictFromRO(odrv) - for key in globals()['odrives'].keys(): - odriveDict[key] = dictFromRO(globals()['odrives'][key]) + for key in globals()['odrives_status'].keys(): + if globals()['odrives_status'][key] == True: + odriveDict[key] = dictFromRO(globals()['odrives'][key]) globals()['inUse'] = False emit('odrives', json.dumps(odriveDict)) @@ -126,10 +134,11 @@ def get_property(message): # will be {"path": "odriveX.axisY.blah.blah"} while globals()['inUse']: time.sleep(0.1) - globals()['inUse'] = True - val = getVal(globals()['odrives'], message["path"].split('.')) - globals()['inUse'] = False - emit('ODriveProperty', json.dumps({"path": message["path"], "val": val})) + if globals()['odrives_status'][message["path"].split('.')[0]]: + globals()['inUse'] = True + val = getVal(globals()['odrives'], message["path"].split('.')) + globals()['inUse'] = False + emit('ODriveProperty', json.dumps({"path": message["path"], "val": val})) @socketio.on('setProperty') def set_property(message): @@ -194,7 +203,7 @@ def postVal(odrives, keyList, value, argType): else: pass # dont support that type yet except fibre.protocol.ChannelBrokenException: - handle_disconnect() + handle_disconnect(odrv) except: print("exception in postVal") @@ -210,7 +219,7 @@ def getVal(odrives, keyList): else: return RO.get_value() except fibre.protocol.ChannelBrokenException: - handle_disconnect() + handle_disconnect(odrv) except: print("exception in getVal") return 0 @@ -235,7 +244,7 @@ def callFunc(odrives, keyList): if isinstance(RO, fibre.remote_object.RemoteFunction): RO.__call__() except fibre.protocol.ChannelBrokenException: - handle_disconnect() + handle_disconnect(odrv) except: print("fcn call failed") @@ -252,7 +261,11 @@ if __name__ == "__main__": import odrive.utils # for dump_errors() import fibre + # global for holding references to all connected odrives globals()['odrives'] = {} + # global dict {'odriveX': True/False} where True/False reflects status of connection + # on handle_disconnect, set it to False. On connection, set it to True + globals()['odrives_status'] = {} globals()['discovered_devices'] = [] # spinlock globals()['inUse'] = False diff --git a/GUI/src/App.vue b/GUI/src/App.vue index 2c9f52e6..fd64070a 100644 --- a/GUI/src/App.vue +++ b/GUI/src/App.vue @@ -56,9 +56,6 @@ :axis="axis.name" :odrives="odrives" > - @@ -129,22 +126,6 @@ export default { currentDash: function () { return this.$store.state.currentDash; }, - ODriveConnected: function () { - // if server and odrive disconnected, disconnected - // if server connected and odrive disco, connecting - // if server and odrive connected, connected - let ret; - if (this.$store.state.serverConnected && this.$store.state.ODriveConnected) { - ret = "connected"; - } - else if (this.$store.state.serverConnected && !this.$store.state.ODriveConnected) { - ret = "connecting..."; - } - else { - ret = "disconnected"; - } - return ret; - }, samplingText: function () { let ret; if (this.$store.state.sampling) { diff --git a/GUI/src/components/Axis.vue b/GUI/src/components/Axis.vue index 2ee3503a..8d0806ba 100644 --- a/GUI/src/components/Axis.vue +++ b/GUI/src/components/Axis.vue @@ -2,7 +2,7 @@
{{ axis }}
@@ -110,6 +110,9 @@ export default { }; }, computed: { + connected() { + return this.$store.state.ODrivesConnected[this.axis.split('.')[0]]; + }, axisErrorMsg() { let retMsg = "none"; let errCode = this.axisErr; @@ -229,14 +232,18 @@ export default { created() { // set up timeout loop for grabbing axis error values let update = () => { - fetchParam(this.axis + ".error"); - fetchParam(this.axis + '.motor.error'); - fetchParam(this.axis + '.controller.error'); - fetchParam(this.axis + '.encoder.error'); - this.axisErr = getVal(this.axis + '.error'); - this.motorErr = getVal(this.axis + '.motor.error'); - this.controllerErr = getVal(this.axis + '.controller.error'); - this.encoderErr = getVal(this.axis + '.encoder.error'); + // Do we have an active connection to the ODrive that contains this axis? + if (this.$store.state.ODrivesConnected[this.axis.split('.')[0]]) { + fetchParam(this.axis + ".error"); + fetchParam(this.axis + '.motor.error'); + fetchParam(this.axis + '.controller.error'); + fetchParam(this.axis + '.encoder.error'); + this.axisErr = getVal(this.axis + '.error'); + this.motorErr = getVal(this.axis + '.motor.error'); + this.controllerErr = getVal(this.axis + '.controller.error'); + this.encoderErr = getVal(this.axis + '.encoder.error'); + } + // ODrive not connected setTimeout(update, 1000); } update(); @@ -270,4 +277,8 @@ export default { color: black; margin-left: 0px; } + +.inactive { + color: grey; +} \ No newline at end of file diff --git a/GUI/src/components/actions/Action.vue b/GUI/src/components/actions/Action.vue index ce896c31..d78948b6 100644 --- a/GUI/src/components/actions/Action.vue +++ b/GUI/src/components/actions/Action.vue @@ -40,7 +40,7 @@ export default { methods: { newVal: function (e) { let val = parseMath(e.target.value); - if (val != false) { + if (val !== false) { this.value = val; console.log("input = " + e.target.value + ", val = " + this.value); this.$store.commit("setActionVal", {dashID: this.dashID, actionID: this.id, val: this.value}); diff --git a/GUI/src/components/controls/CtrlNumeric.vue b/GUI/src/components/controls/CtrlNumeric.vue index 752dbce6..2e3af0a6 100644 --- a/GUI/src/components/controls/CtrlNumeric.vue +++ b/GUI/src/components/controls/CtrlNumeric.vue @@ -47,8 +47,9 @@ export default { putVal: function (e) { let keys = this.path.split('.'); keys.shift(); + console.log("input recieved: " + e.target.value); let val = parseMath(e.target.value); - if (val != false) { + if (val !== false) { putVal(keys.join('.'), val); } }, diff --git a/GUI/src/components/wizard/choices/wizardBrake.vue b/GUI/src/components/wizard/choices/wizardBrake.vue index caef2057..42a69e73 100644 --- a/GUI/src/components/wizard/choices/wizardBrake.vue +++ b/GUI/src/components/wizard/choices/wizardBrake.vue @@ -37,7 +37,7 @@ export default { setBR(e) { console.log("from setBR " + e.target.value); let val = parseMath(e.target.value); - if (val != false) { + if (val !== false) { this.brake_resistance = val; let configStub = undefined; configStub = { diff --git a/GUI/src/components/wizard/choices/wizardEncoderIncremental.vue b/GUI/src/components/wizard/choices/wizardEncoderIncremental.vue index 6395f921..2c296abd 100644 --- a/GUI/src/components/wizard/choices/wizardEncoderIncremental.vue +++ b/GUI/src/components/wizard/choices/wizardEncoderIncremental.vue @@ -26,7 +26,7 @@ export default { methods: { setCPR(e) { let val = parseMath(e.target.value); - if (val != false) { + if (val !== false) { this.cpr = val; let configStub = undefined; if (this.data.axis == "axis0") { diff --git a/GUI/src/components/wizard/choices/wizardEncoderIncrementalIndex.vue b/GUI/src/components/wizard/choices/wizardEncoderIncrementalIndex.vue index 79baa12a..85357d41 100644 --- a/GUI/src/components/wizard/choices/wizardEncoderIncrementalIndex.vue +++ b/GUI/src/components/wizard/choices/wizardEncoderIncrementalIndex.vue @@ -27,7 +27,7 @@ export default { methods: { setCPR(e) { let val = parseMath(e.target.value); - if (val != false) { + if (val !== false) { this.cpr = val; let configStub = undefined; if (this.data.axis == "axis0") { diff --git a/GUI/src/components/wizard/choices/wizardMisc.vue b/GUI/src/components/wizard/choices/wizardMisc.vue index 9aa8f39f..f5aaa484 100644 --- a/GUI/src/components/wizard/choices/wizardMisc.vue +++ b/GUI/src/components/wizard/choices/wizardMisc.vue @@ -100,7 +100,7 @@ export default { }, setVelocityLimit(e) { let val = parseMath(e.target.value); - if (val != false) { + if (val !== false) { this.vel_limit = parseFloat(e.target.value); this.vel_set = true; this.sendConfig(); @@ -108,7 +108,7 @@ export default { }, setCurrentLimit(e) { let val = parseMath(e.target.value); - if (val != false) { + if (val !== false) { this.current_lim = parseFloat(e.target.value); this.current_set = true; this.sendConfig(); diff --git a/GUI/src/lib/odrive_utils.js b/GUI/src/lib/odrive_utils.js index 8f1993e2..e022425a 100644 --- a/GUI/src/lib/odrive_utils.js +++ b/GUI/src/lib/odrive_utils.js @@ -6,11 +6,17 @@ import * as socketio from "../comms/socketio.js"; // given a path like "odrive0.axis0.config.blah", return the value export function getParam(path) { let keys = path.split('.'); - let odriveObj = store.state.odrives; - for (const key of keys) { - odriveObj = odriveObj[key]; + if (store.state.ODrivesConnected[keys[0]]) { + let odriveObj = store.state.odrives; + for (const key of keys) { + odriveObj = odriveObj[key]; + } + return odriveObj; + } + else { + console.log("getParam for " + path + " is for disconnected ODrive"); + return undefined; } - return odriveObj; } // wrapper for val field @@ -51,10 +57,15 @@ export function parseMath(inString) { export function putVal(path, value) { console.log("path: " + path + ", val: " + value + ", type: " + typeof value); - socketio.sendEvent({ - type: "setProperty", - data: {path: path, val: value, type: typeof value} - }) + if (store.state.ODrivesConnected[path.split('.')[0]]) { + socketio.sendEvent({ + type: "setProperty", + data: {path: path, val: value, type: typeof value} + }); + } + else { + console.log("requesting " + path + " from disconnected odrive") + } } // path is path to function, args is list of parameters diff --git a/GUI/src/store.js b/GUI/src/store.js index 21b10b97..16be7562 100644 --- a/GUI/src/store.js +++ b/GUI/src/store.js @@ -21,7 +21,7 @@ export default new Vuex.Store({ axes: Array, odriveServerAddress: String, serverConnected: Boolean, - ODriveConnected: false, + ODrivesConnected: Object, serverOutput: [], dashboards: [ { @@ -95,6 +95,13 @@ export default new Vuex.Store({ state.odriveConfigs['writeAble'] = payload.writeAble; state.odriveConfigs['writeAbleNumeric'] = payload.writeAbleNumeric; }, + setODrivesStatus(state, obj) { + // obj is {"odriveX": true/false} + for (const odrive of Object.keys(obj)){ + state.ODrivesConnected[odrive] = obj[odrive]; + console.log(state.ODrivesConnected); + } + }, setAxes(state, axes) { state.axes = axes; }, @@ -163,9 +170,6 @@ export default new Vuex.Store({ setServerStatus(state, val) { state.serverConnected = val; }, - setODriveConnected(state, val) { - state.ODriveConnected = val; - }, removeCtrlFromDash(state, obj) { // obj is {dash: dashID, path: control path} for (const dash of state.dashboards) { @@ -325,7 +329,6 @@ export default new Vuex.Store({ type: "odrive-found", callback: () => { console.log("odrive-found recieved from server"); - context.commit("setODriveConnected", true); context.dispatch("getOdrives"); } }) @@ -370,15 +373,21 @@ export default new Vuex.Store({ }); socketio.addEventListener({ type: "odrive-disconnected", - callback: () => { - console.log("odrive disconnected"); - context.commit("setODriveConnected", false); - console.log("restarting server..."); - window.ipcRenderer.send('kill-server'); - window.ipcRenderer.send('start-server'); - context.dispatch('setServerAddress', context.state.odriveServerAddress); + callback: (odrive_name) => { + console.log(odrive_name + " disconnected"); + //console.log("restarting server..."); + //window.ipcRenderer.send('kill-server'); + //window.ipcRenderer.send('start-server'); + //context.dispatch('setServerAddress', context.state.odriveServerAddress); } - }) + }); + socketio.addEventListener({ + type: "odrives-status", + callback: (odrives_status) => { + console.log("From odrives-status msg " + odrives_status); + context.commit('setODrivesStatus', JSON.parse(odrives_status)); + } + }); } } }) \ No newline at end of file From 0aa32b012c5b98cad8666b98a9c66c647777355e Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Wed, 28 Oct 2020 19:32:08 -0400 Subject: [PATCH 10/14] [GUI] Robustified the python version check --- GUI/src/background.js | 50 +++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/GUI/src/background.js b/GUI/src/background.js index c21e39a4..36fbca98 100644 --- a/GUI/src/background.js +++ b/GUI/src/background.js @@ -23,25 +23,43 @@ protocol.registerSchemesAsPrivileged([ // function to get determine correct command for python function getPyCmd() { - let spawnRet = spawnSync('python',['-V']); - let vString; - if (spawnRet.stdout.toString().length > 1){ - vString = spawnRet.stdout.toString(); - } - else { - vString = spawnRet.stderr.toString(); - } - - if (vString.split(' ')[1].split('.')[0] == '2') { - return 'python3'; - } - else { + // call both 'python' and 'python3' to figure out the correct command + let spawnRet = spawnSync('python', ['-V']); + let success = spawnRet.status != null; + let outputString; + if (success) { + if (spawnRet.stdout.toString().length > 1) { + outputString = spawnRet.stdout.toString(); + } + else { + outputString = spawnRet.stderr.toString(); + } + if (outputString.includes("Python 3")) { return 'python'; + } + } + else { + spawnRet = spawnSync('python3', ['-V']); + success = spawnRet.status != null; + if (success) { + if (spawnRet.stdout.toString().length > 1) { + outputString = spawnRet.stdout.toString(); + } + else { + outputString = spawnRet.stderr.toString(); + } + if (outputString.includes("Python 3")) { + return 'python3'; + } + } + else { + return ''; + } } } function createWindow() { - + // Create the browser window. win = new BrowserWindow({ width: 800, @@ -125,7 +143,7 @@ app.on('ready', async () => { // launch python server on event from renderer process (gui) and pipe stdout/stderr to it ipcMain.on('start-server', () => { server = spawn(getPyCmd(), effectiveCommand); - server.stdout.on('data',function(data) { + server.stdout.on('data', function (data) { console.log(data.toString('utf8')); try { win.webContents.send('server-stdout', String(data.toString('utf8'))); @@ -133,7 +151,7 @@ app.on('ready', async () => { console.log(error); } }); - server.stderr.on('data',function(data) { + server.stderr.on('data', function (data) { console.log(data.toString('utf8')); try { win.webContents.send('server-stderr', String(data.toString('utf8'))); From e9d5f55570c367ccc0bd15cfb4964250d786729b Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Thu, 29 Oct 2020 00:20:39 -0400 Subject: [PATCH 11/14] Fixed the encoder offset calibration to work correctly when calib_scan_distance is not a multiple of 4pi --- CHANGELOG.md | 2 +- Firmware/MotorControl/encoder.cpp | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b294c67..ea1c76d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Added periodic sending of encoder position on CAN ### Changed - +* Modified encoder offset calibration to work correctly when calib_scan_distance is not a multiple of 4pi * Moved thermistors from being a top level object to belonging to Motor objects. Also changed errors: thermistor errors rolled into motor errors * Use DMA for DRV8301 setup * Make NVM configuration code more dynamic so that the layout doesn't have to be known at compile time. diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index d2ce992b..cc5c2d1d 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -228,10 +228,13 @@ bool Encoder::run_offset_calibration() { else return false; - // go to motor zero phase for start_lock_duration to get ready to scan + // go to start position of forward scan for start_lock_duration to get ready to scan int i = 0; axis_->run_control_loop([&](){ - if (!axis_->motor_.enqueue_voltage_timings(voltage_magnitude, 0.0f)) + float phase = wrap_pm_pi(0 - config_.calib_scan_distance / 2.0f); + float v_alpha = voltage_magnitude * our_arm_cos_f32(phase); + 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(TIMING_LOG_ENC_CALIB); return ++i < start_lock_duration * current_meas_hz; From bf2fd35d934945e183cba3481a31d1884cbef65e Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Thu, 29 Oct 2020 00:50:46 -0400 Subject: [PATCH 12/14] [GUI] actually fixed python command determination in background.js --- GUI/src/background.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/GUI/src/background.js b/GUI/src/background.js index 36fbca98..382155fe 100644 --- a/GUI/src/background.js +++ b/GUI/src/background.js @@ -27,6 +27,7 @@ function getPyCmd() { let spawnRet = spawnSync('python', ['-V']); let success = spawnRet.status != null; let outputString; + let cmd = ''; if (success) { if (spawnRet.stdout.toString().length > 1) { outputString = spawnRet.stdout.toString(); @@ -35,10 +36,10 @@ function getPyCmd() { outputString = spawnRet.stderr.toString(); } if (outputString.includes("Python 3")) { - return 'python'; + cmd = 'python'; } } - else { + if (cmd == '') { spawnRet = spawnSync('python3', ['-V']); success = spawnRet.status != null; if (success) { @@ -49,13 +50,11 @@ function getPyCmd() { outputString = spawnRet.stderr.toString(); } if (outputString.includes("Python 3")) { - return 'python3'; + cmd = 'python3'; } } - else { - return ''; - } } + return cmd; } function createWindow() { @@ -144,7 +143,11 @@ app.on('ready', async () => { ipcMain.on('start-server', () => { server = spawn(getPyCmd(), effectiveCommand); server.stdout.on('data', function (data) { - console.log(data.toString('utf8')); + try { + console.log(data.toString('utf8')); + } catch (error) { + console.log(error); + } try { win.webContents.send('server-stdout', String(data.toString('utf8'))); } catch (error) { From efa8b30822fa749e895fb043dd12153f5381193a Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Fri, 30 Oct 2020 01:49:25 -0400 Subject: [PATCH 13/14] [GUI] refactored motor and encoder calibration into async functions. --- .../wizard/page_components/wizardMotorCal.vue | 7 +- GUI/src/lib/odrive_utils.js | 110 ++++++++++ GUI/src/lib/utils.js | 25 +++ GUI/src/views/Wizard.vue | 200 ++++++------------ 4 files changed, 197 insertions(+), 145 deletions(-) diff --git a/GUI/src/components/wizard/page_components/wizardMotorCal.vue b/GUI/src/components/wizard/page_components/wizardMotorCal.vue index 84dfc84f..43961c4d 100644 --- a/GUI/src/components/wizard/page_components/wizardMotorCal.vue +++ b/GUI/src/components/wizard/page_components/wizardMotorCal.vue @@ -5,8 +5,8 @@