diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 18f8786e..7bc8b8db 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -2,7 +2,7 @@ name: Build and publish HTML documentation website on: push: - branches: [ feature/doc_autogen ] + branches: [ master ] jobs: jekyll: @@ -52,15 +52,22 @@ jobs: cd /srv/jekyll/docs && \ bundle config path vendor/bundle && \ bundle install && \ - JEKYLL_ENV=production bundle exec jekyll build + bundle exec jekyll build --baseurl \"\" + cd .. + mv docs/_site _site + rm -rdf docs + mv _site docs + touch docs/.nojekyll " - touch .nojekyll + # Extra checks to reduce likelihood of defect build + test -f docs/CNAME + test -f docs/index.html - name: Push to documentation branch run: | git config user.name "${GITHUB_ACTOR}" git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" - git add -f docs/_site + git add -f docs git commit -m "jekyll build from Action ${GITHUB_SHA}" git push --force origin HEAD:${REMOTE_BRANCH} env: diff --git a/.gitignore b/.gitignore index cbbbfd24..6ec40973 100644 --- a/.gitignore +++ b/.gitignore @@ -62,8 +62,7 @@ ODrive\.includes Firmware/Tests/bin/ -# Electron-builder output +# GUI GUI/dist_electron - -# Node Modules GUI/node_modules +GUI/build diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f7831d4..0652edf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased Features Please add a note of your changes below this heading if you make a Pull Request. +### Added +* [Mechanical brake support](docs/mechanical-brakes.md) ### Changed @@ -15,8 +17,8 @@ Please add a note of your changes below this heading if you make a Pull Request. * `.motor.gate_driver` was moved to `.gate_driver`. * `.min_endstop.pullup` and `.max_endstop.pullup` were removed. Use `.config.gpioX_mode = GPIO_MODE_DIGITAL / GPIO_MODE_DIGITAL_PULL_UP / GPIO_MODE_DIGITAL_PULL_DOWN` instead. -# Release Candidate -## [0.5.1] - Date TBD +# Releases +## [0.5.1] - 2020-09-27 ### Added * Added motor `torque_constant`: units of torque are now [Nm] instead of just motor current. * [Motor thermistors support](docs/thermistors.md) @@ -29,9 +31,14 @@ Please add a note of your changes below this heading if you make a Pull Request. * `axis.motor.thermal_current_lim` has been removed. Instead a new property is available `axis.motor.effective_current_lim` which contains the effective current limit including any thermal limits. * `axis.motor.get_inverter_temp()`, `axis.motor.inverter_temp_limit_lower` and `axis.motor.inverter_temp_limit_upper` have been moved to seperate fet thermistor object under `axis.fet_thermistor`. `get_inverter_temp()` function has been renamed to `temp` and is now a read-only property. * `axis.config.counts_per_step` is now `axis.config.turns_per_step` +* Outputs of `axis.sensorless_estimator` are now in turns/s instead of electrical rad/s +* Fixed bug of high current during lockin-ramp caused by `motor::update()` expecting a torque command instead of current +* Fixed bug where commanded velocity was extremely high just after sensorless ramp when using `input_mode` INPUT_MODE_VEL_RAMP caused by `vel_setpoint` and `axis.config.sensorless_ramp.vel` being in different units +### Fixed +* Fixed bug of high current during lockin-ramp caused by `motor::update()` expecting a torque command instead of current +* Fixed bug where commanded velocity was extremely high just after sensorless ramp when using `input_mode` INPUT_MODE_VEL_RAMP caused by `vel_setpoint` and `axis.config.sensorless_ramp.vel` being in different units -# Releases ## [0.5.0] - 2020-08-03 ### Added * AC Induction Motor support. diff --git a/Dockerfile b/Dockerfile index 9739928c..8810dc23 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,16 +7,22 @@ RUN add-apt-repository ppa:team-gcc-arm-embedded/ppa RUN add-apt-repository ppa:jonathonf/tup RUN apt-get update RUN apt-get -y upgrade -RUN apt-get -y install gcc-arm-embedded openocd tup python3.7 build-essential git +RUN apt-get -y install gcc-arm-embedded openocd tup python3.7 python3-yaml python3-jinja2 python3-jsonschema build-essential git # Build step below does not know about debian's python naming schemme RUN ln -s /usr/bin/python3.7 /usr/bin/python -# Copy the firmware tree into the container -RUN mkdir ODrive -COPY . ODrive +RUN mkdir -p ODrive WORKDIR ODrive/Firmware -# Hack around Tup's dependency on FUSE -RUN tup generate build.sh -RUN ./build.sh +# Must attach the firmware tree into the container +CMD \ + # Regenerate python interface + python interface_generator_stub.py \ + --definitions odrive-interface.yaml \ + --template ../tools/enums_template.j2 \ + --output ../tools/odrive/enums.py && \ + # Hack around Tup's dependency on FUSE + tup init && \ + tup generate build.sh && \ + ./build.sh diff --git a/Firmware/Board/v3/Src/stm32f4xx_it.c b/Firmware/Board/v3/Src/stm32f4xx_it.c index 1e69bcb9..69feb861 100644 --- a/Firmware/Board/v3/Src/stm32f4xx_it.c +++ b/Firmware/Board/v3/Src/stm32f4xx_it.c @@ -35,6 +35,7 @@ #include "stm32f4xx.h" #include "stm32f4xx_it.h" #include "cmsis_os.h" +#include /* USER CODE BEGIN 0 */ #include @@ -85,6 +86,13 @@ void get_regs(void** stack_ptr) { void* volatile pc __attribute__((unused)) = stack_ptr[6]; // Program counter void* volatile psr __attribute__((unused)) = stack_ptr[7]; // Program status register + void* volatile cfsr __attribute__((unused)) = (void*)SCB->CFSR; // Configurable fault status register + void* volatile cpacr __attribute__((unused)) = (void*)SCB->CPACR; + void* volatile fpccr __attribute__((unused)) = (void*)FPU->FPCCR; + + volatile bool preciserr __attribute__((unused)) = (uint32_t)cfsr & 0x200; + volatile bool ibuserr __attribute__((unused)) = (uint32_t)cfsr & 0x100; + volatile int stay_looping = 1; while(stay_looping); } diff --git a/Firmware/Board/v3/board.cpp b/Firmware/Board/v3/board.cpp index 5f883809..d2b0afbb 100644 --- a/Firmware/Board/v3/board.cpp +++ b/Firmware/Board/v3/board.cpp @@ -96,6 +96,7 @@ Encoder encoders[AXIS_COUNT] = { // TODO: this has no hardware dependency and should be allocated depending on config Endstop endstops[2 * AXIS_COUNT]; +MechanicalBrake mechanical_brakes[AXIS_COUNT]; SensorlessEstimator sensorless_estimators[AXIS_COUNT]; Controller controllers[AXIS_COUNT]; @@ -116,6 +117,7 @@ std::array axes{{ motors[0], // motor trap[0], // trap endstops[0], endstops[1], // min_endstop, max_endstop + mechanical_brakes[0], // mechanical brake }, { 1, // axis_num @@ -135,6 +137,7 @@ std::array axes{{ motors[1], // motor trap[1], // trap endstops[2], endstops[3], // min_endstop, max_endstop + mechanical_brakes[1], // mechanical brake }, }}; diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index e7e8d0bb..52150800 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -19,7 +19,8 @@ Axis::Axis(int axis_num, Motor& motor, TrapezoidalTrajectory& trap, Endstop& min_endstop, - Endstop& max_endstop) + Endstop& max_endstop, + MechanicalBrake& mechanical_brake) : axis_num_(axis_num), default_step_gpio_pin_(default_step_gpio_pin), default_dir_gpio_pin_(default_dir_gpio_pin), @@ -33,6 +34,7 @@ Axis::Axis(int axis_num, 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))), @@ -49,6 +51,7 @@ Axis::Axis(int axis_num, trap_traj_.axis_ = this; min_endstop_.axis_ = this; max_endstop_.axis_ = this; + mechanical_brake_.axis_ = this; } Axis::LockinConfig_t Axis::default_calibration() { @@ -235,9 +238,9 @@ bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) { float x = 0.0f; run_control_loop([&]() { float phase = wrap_pm_pi(lockin_config.ramp_distance * x); - float I_mag = lockin_config.current * x; + float torque = lockin_config.current * motor_.config_.torque_constant * x; x += current_meas_period / lockin_config.ramp_time; - if (!motor_.update(I_mag, phase, 0.0f)) + if (!motor_.update(torque, phase, 0.0f)) return false; return x < 1.0f; }); @@ -266,7 +269,7 @@ bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) { distance += vel * current_meas_period; phase = wrap_pm_pi(phase + vel * current_meas_period); - if (!motor_.update(lockin_config.current, phase, vel)) + if (!motor_.update(lockin_config.current * motor_.config_.torque_constant, phase, vel)) return false; return !spin_done(true); //vel_override to go to next phase }); @@ -282,7 +285,7 @@ bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) { distance += vel * current_meas_period; phase = wrap_pm_pi(phase + vel * current_meas_period); - if (!motor_.update(lockin_config.current, phase, vel)) + if (!motor_.update(lockin_config.current * motor_.config_.torque_constant, phase, vel)) return false; return !spin_done(); }); @@ -460,6 +463,7 @@ bool Axis::run_idle_loop() { // run_control_loop ignores missed modulation timing updates // if and only if we're in AXIS_STATE_IDLE safety_critical_disarm_motor_pwm(motor_); + mechanical_brake_.engage(); set_step_dir_active(config_.enable_step_dir && config_.step_dir_always_on); run_control_loop([this]() { return true; @@ -472,6 +476,7 @@ void Axis::run_state_machine_loop() { // arm! motor_.arm(); + mechanical_brake_.release(); for (;;) { // Load the task chain if a specific request is pending @@ -556,7 +561,7 @@ void Axis::run_state_machine_loop() { if (status) { // call to controller.reset() that happend when arming means that vel_setpoint // is zeroed. So we make the setpoint the spinup target for smooth transition. - controller_.vel_setpoint_ = config_.sensorless_ramp.vel; + controller_.vel_setpoint_ = config_.sensorless_ramp.vel / (2.0f * M_PI * motor_.config_.pole_pairs); status = run_sensorless_control_loop(); } } break; @@ -573,6 +578,7 @@ void Axis::run_state_machine_loop() { case AXIS_STATE_IDLE: { run_idle_loop(); status = motor_.arm(); // done with idling - try to arm the motor + mechanical_brake_.release(); } break; default: diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index f9e4eb7d..b3965b39 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -8,6 +8,7 @@ class Axis; #include "controller.hpp" #include "trapTraj.hpp" #include "endstop.hpp" +#include "mechanical_brake.hpp" #include "low_level.h" #include "utils.hpp" #include "communication/interface_uart.h" // TODO: remove once uart_poll() is gone @@ -90,7 +91,8 @@ public: Motor& motor, TrapezoidalTrajectory& trap, Endstop& min_endstop, - Endstop& max_endstop); + Endstop& max_endstop, + MechanicalBrake& mechanical_brake); bool apply_config(); void clear_config(); @@ -220,6 +222,7 @@ public: TrapezoidalTrajectory& trap_traj_; Endstop& min_endstop_; Endstop& max_endstop_; + MechanicalBrake& mechanical_brake_; // List of current_limiters and thermistors to // provide easy iteration. diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index a40d2ac6..3a611971 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -380,7 +380,7 @@ void vbus_sense_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { } // This is the callback from the ADC that we expect after the PWM has triggered an ADC conversion. -// TODO: Document how the phasing is done, link to timing diagram +// Timing diagram: Firmware/timing_diagram_v3.png void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { #define calib_tau 0.2f //@TOTO make more easily configurable constexpr float calib_filter_k = CURRENT_MEAS_PERIOD / calib_tau; diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 01a37299..27d7be86 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -48,6 +48,7 @@ static bool config_read_all() { config_manager.read(&axes[i].trap_traj_.config_) && config_manager.read(&axes[i].min_endstop_.config_) && 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_) && @@ -67,6 +68,7 @@ static bool config_write_all() { config_manager.write(&axes[i].trap_traj_.config_) && config_manager.write(&axes[i].min_endstop_.config_) && 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_) && @@ -86,6 +88,7 @@ static void config_clear_all() { axes[i].trap_traj_.config_ = {}; axes[i].min_endstop_.config_ = {}; axes[i].max_endstop_.config_ = {}; + axes[i].mechanical_brake_.config_ = {}; motors[i].config_ = {}; fet_thermistors[i].config_ = {}; axes[i].motor_thermistor_.config_ = {}; @@ -152,7 +155,7 @@ void ODrive::enter_dfu_mode() { static void usb_deferred_interrupt_thread(void * ctx) { (void) ctx; // unused parameter - + for (;;) { // Wait for signalling from USB interrupt (OTG_FS_IRQHandler) osStatus semaphore_status = osSemaphoreWait(sem_usb_irq, osWaitForever); @@ -419,6 +422,7 @@ extern "C" int main(void) { if (mode == ODriveIntf::GPIO_MODE_DIGITAL || mode == ODriveIntf::GPIO_MODE_DIGITAL_PULL_UP || mode == ODriveIntf::GPIO_MODE_DIGITAL_PULL_DOWN || + mode == ODriveIntf::GPIO_MODE_MECH_BRAKE || mode == ODriveIntf::GPIO_MODE_ANALOG_IN) { GPIO_InitStruct.Alternate = 0; } else { @@ -515,6 +519,11 @@ extern "C" int main(void) { GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; } break; + case ODriveIntf::GPIO_MODE_MECH_BRAKE: { + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + } break; default: { odrv.misconfigured_ = true; continue; diff --git a/Firmware/MotorControl/mechanical_brake.cpp b/Firmware/MotorControl/mechanical_brake.cpp new file mode 100644 index 00000000..c9d3602f --- /dev/null +++ b/Firmware/MotorControl/mechanical_brake.cpp @@ -0,0 +1,13 @@ +#include + +void MechanicalBrake::engage() { + if (odrv.config_.gpio_modes[config_.gpio_num] == ODriveIntf::GPIO_MODE_MECH_BRAKE){ + get_gpio(config_.gpio_num).write(config_.is_active_low ? 0 : 1); + } +} + +void MechanicalBrake::release() { + if (odrv.config_.gpio_modes[config_.gpio_num] == ODriveIntf::GPIO_MODE_MECH_BRAKE){ + get_gpio(config_.gpio_num).write(config_.is_active_low ? 1 : 0); + } +} diff --git a/Firmware/MotorControl/mechanical_brake.hpp b/Firmware/MotorControl/mechanical_brake.hpp new file mode 100644 index 00000000..26362ce5 --- /dev/null +++ b/Firmware/MotorControl/mechanical_brake.hpp @@ -0,0 +1,25 @@ +#ifndef __MECHANICAL_BRAKE_HPP +#define __MECHANICAL_BRAKE_HPP + +#include + +class MechanicalBrake : public ODriveIntf::MechanicalBrakeIntf { + public: + struct Config_t { + uint16_t gpio_num = 0; + bool is_active_low = true; + + // custom setters + MechanicalBrake* parent = nullptr; + void set_gpio_num(uint16_t value) { gpio_num = value; } + }; + + MechanicalBrake() {} + + MechanicalBrake::Config_t config_; + Axis* axis_ = nullptr; + + void release(); + void engage(); +}; +#endif // __MECHANICAL_BRAKE_HPP \ No newline at end of file diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 9bdbaa75..355067f5 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -49,6 +49,7 @@ void Motor::reset_current_control() { current_control_.v_current_control_integral_d = 0.0f; current_control_.v_current_control_integral_q = 0.0f; current_control_.acim_rotor_flux = 0.0f; + current_control_.Ibus = 0.0f; } // @brief Tune the current controller based on phase resistance and inductance diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 2a5c9f5c..a3c33a1d 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -142,6 +142,7 @@ inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast(~static_c #include #include #include +#include #include #include diff --git a/Firmware/MotorControl/sensorless_estimator.cpp b/Firmware/MotorControl/sensorless_estimator.cpp index d70c7d2d..878b6ee7 100644 --- a/Firmware/MotorControl/sensorless_estimator.cpp +++ b/Firmware/MotorControl/sensorless_estimator.cpp @@ -66,13 +66,15 @@ bool SensorlessEstimator::update() { } // predict PLL phase with velocity - pll_pos_ = wrap_pm_pi(pll_pos_ + current_meas_period * vel_estimate_); + pll_pos_ = wrap_pm_pi(pll_pos_ + current_meas_period * vel_estimate_erad_); // update PLL phase with observer permanent magnet phase phase_ = fast_atan2(eta[1], eta[0]); float delta_phase = wrap_pm_pi(phase_ - pll_pos_); pll_pos_ = wrap_pm_pi(pll_pos_ + current_meas_period * pll_kp * delta_phase); // update PLL velocity - vel_estimate_ += current_meas_period * pll_ki * delta_phase; + vel_estimate_erad_ += current_meas_period * pll_ki * delta_phase; + // convert to mechanical turns/s for controller usage. + vel_estimate_ = vel_estimate_erad_ / (std::max((float)axis_->motor_.config_.pole_pairs, 1.0f) * 2.0f * M_PI); vel_estimate_valid_ = true; return true; diff --git a/Firmware/MotorControl/sensorless_estimator.hpp b/Firmware/MotorControl/sensorless_estimator.hpp index 9f59b28a..a0a6ec3c 100644 --- a/Firmware/MotorControl/sensorless_estimator.hpp +++ b/Firmware/MotorControl/sensorless_estimator.hpp @@ -18,7 +18,8 @@ public: Error error_ = ERROR_NONE; float phase_ = 0.0f; // [rad] float pll_pos_ = 0.0f; // [rad] - float vel_estimate_ = 0.0f; // [rad/s] + float vel_estimate_ = 0.0f; // [turn/s] + float vel_estimate_erad_ = 0.0f; // [rad/s] bool vel_estimate_valid_ = false; // float pll_kp_ = 0.0f; // [rad/s / rad] // float pll_ki_ = 0.0f; // [(rad/s^2) / rad] diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 040db512..84412357 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -133,6 +133,7 @@ FLAGS += '-DUSE_HAL_DRIVER' FLAGS += '-mthumb' FLAGS += '-mfloat-abi=hard' FLAGS += { '-Wall', '-Wdouble-promotion', '-Wfloat-conversion', '-fdata-sections', '-ffunction-sections'} +FLAGS += '-g' -- linker flags LDFLAGS += board.ldflags @@ -142,7 +143,7 @@ LDFLAGS += '-Wl,--undefined=uxTopUsedPriority' -- debug build if tup.getconfig("DEBUG") == "true" then - FLAGS += '-g -gdwarf-2' + FLAGS += '-gdwarf-2' OPT += '-Og' else OPT += '-O2' @@ -189,6 +190,7 @@ sources = { 'MotorControl/thermistor.cpp', 'MotorControl/encoder.cpp', 'MotorControl/endstop.cpp', + 'MotorControl/mechanical_brake.cpp', 'MotorControl/controller.cpp', 'MotorControl/sensorless_estimator.cpp', 'MotorControl/trapTraj.cpp', diff --git a/Firmware/fibre/tools/interface_generator.py b/Firmware/fibre/tools/interface_generator.py index 659d97f6..2dcf9c83 100644 --- a/Firmware/fibre/tools/interface_generator.py +++ b/Firmware/fibre/tools/interface_generator.py @@ -328,7 +328,7 @@ def regularize_valuetype(path, name, elem): elem['flags'][k]['value'] = 0 if current_bit is None else (1 << current_bit) bit = bit if current_bit is None else current_bit + 1 if 'nullflag' in elem: - elem['flags'] = OrderedDict([(elem['nullflag'], {'value': 0, 'bit': None}), *elem['flags'].items()]) + elem['flags'] = OrderedDict([(elem['nullflag'], OrderedDict({'name': elem['nullflag'], 'value': 0, 'bit': None})), *elem['flags'].items()]) elem['values'] = elem['flags'] elem['is_flags'] = True elem['is_enum'] = True @@ -653,6 +653,7 @@ env.filters['skip_first'] = lambda x: list(x)[1:] env.filters['to_c_string'] = lambda x: '\n'.join(('"' + line.replace('"', '\\"') + '"') for line in json.dumps(x, separators=(',', ':')).replace('{"name"', '\n{"name"').split('\n')) env.filters['tokenize'] = tokenize env.filters['diagonalize'] = lambda lst: [lst[:i + 1] for i in range(len(lst))] +env.filters['debug'] = lambda x: print(x) template = env.from_string(template_file.read()) diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 1493e899..43126cbd 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -454,6 +454,7 @@ interfaces: trap_traj: TrapezoidalTrajectory min_endstop: Endstop max_endstop: Endstop + mechanical_brake: MechanicalBrake functions: watchdog_feed: doc: Feed the watchdog to prevent watchdog timeouts. @@ -902,7 +903,6 @@ interfaces: accel_limit: float32 decel_limit: float32 - ODrive.Endstop: c_is_class: True attributes: @@ -916,6 +916,21 @@ interfaces: is_active_high: bool debounce_ms: {type: uint32, c_setter: set_debounce_ms} + ODrive.MechanicalBrake: + c_is_class: True + attributes: + config: + c_is_class: False + attributes: + gpio_num: {type: uint16, c_setter: set_gpio_num} + is_active_low: bool + functions: + engage: + doc: | + This function engages the mechanical brake if one is present and enabled. + release: + doc: | + This function releases the mecahncal brake if one is present and enabled. valuetypes: ODrive.GpioMode: @@ -944,6 +959,7 @@ valuetypes: Enc0: {doc: The pin is used by quadrature encoder 0.} Enc1: {doc: The pin is used by quadrature encoder 1.} Enc2: {doc: This mode is not supported on ODrive v3.x.} + MechBrake: {doc: This is to support external mechanical brakes.} ODrive.Can.Protocol: values: {Simple: } diff --git a/Firmware/timing_diagram_v3.png b/Firmware/timing_diagram_v3.png new file mode 100644 index 00000000..f36d0af1 Binary files /dev/null and b/Firmware/timing_diagram_v3.png differ diff --git a/dockerbuild.sh b/dockerbuild.sh index 779b9bf1..acf77e9d 100755 --- a/dockerbuild.sh +++ b/dockerbuild.sh @@ -1,6 +1,6 @@ function cleanup { echo "Removing previous build artifacts" - rm -rf build + rm -rf build/ Firmware/autogen Firmware/build Firmware/.tup docker rm odrive-build-cont } @@ -13,14 +13,11 @@ function gc { function build { cleanup - echo "Building the firmware" + echo "Building the build-environment image" docker build -t odrive-build-img . - echo "Create container" - docker create --name odrive-build-cont odrive-build-img:latest - - echo "Extract build artifacts" - docker cp odrive-build-cont:ODrive/Firmware/build . + echo "Build in container" + docker run -v $(pwd):/ODrive --name odrive-build-cont odrive-build-img:latest } function usage { diff --git a/docs/_data/index.yaml b/docs/_data/index.yaml index 0ff47762..273029b6 100644 --- a/docs/_data/index.yaml +++ b/docs/_data/index.yaml @@ -17,6 +17,8 @@ sections: url: /encoders - title: Homing & Endstops url: /endstops + - title: Mechanical Brakes + url: /mechanical-brakes - title: Thermistors url: /thermistors - title: Control & Tuning diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 5569b3a5..0a63814f 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -16,6 +16,7 @@ +
The docs reflect firmware version 0.5.1. There are many breaking changes. Please find docs for v0.4.12 here.
@@ -78,7 +79,7 @@ {% if page.edit_url %} - {% assign edit_url = "https://www.github.com/madcowswe/ODrive/edit/master/" | append: edit_url %} + {% assign edit_url = "https://www.github.com/madcowswe/ODrive/edit/master/" | append: page.edit_url %} {% else %} {% assign edit_url = "https://www.github.com/madcowswe/ODrive/edit/master/docs/" | append: pagename | append: ".md" %} {% endif %} @@ -90,7 +91,7 @@ - {{page.download.text}} + {{page.download.text}}
{% endif %}
diff --git a/docs/can-protocol.md b/docs/can-protocol.md index 0b3f0c84..5ac54750 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -73,7 +73,7 @@ Configuration of the CAN parameters should be done via USB before putting the de To set the desired baud rate, use `.can.set_baud_rate()`. The baud rate can be done without rebooting the device. If you'd like to keep the baud rate, simply call `.save_configuration()` before rebooting. -Each axis looks like a separate node on the bus. Thus, they both have the two properties `can_node_id` and `can_node_id_extended`. The node ID can be from 0 to 63 (0x3F) inclusive, or, if extended CAN IDs are used, from 0 to 16777215 (0xFFFFFF). +Each axis looks like a separate node on the bus. Thus, they both have the two properties `can_node_id` and `can_node_id_extended`. The node ID can be from 0 to 63 (0x3F) inclusive, or, if extended CAN IDs are used, from 0 to 16777215 (0xFFFFFF). If you want to connect more than one ODrive on a CAN bus, you must set different node IDs for the second ODrive or they will conflict and crash the bus. ### Example Configuration diff --git a/docs/commands.md b/docs/commands.md index 1b41b30f..510c33c6 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -44,6 +44,7 @@ Possible values are listed [here](api/odrive.axis.controller.controlmode). As of version v0.5.0, ODrive now intercepts the incoming commands and can apply filters to them. The old protocol values `pos_setpoint`, `vel_setpoint`, and `current_setpoint` are still used internally by the closed-loop cascade control, but the user cannot write to them directly. This allows us to condense the number of ways the ODrive accepts motion commands. The new commands are: +### Control Commands * `.controller.input_pos = ` * `.controller.input_vel = ` * `.controller.input_torque = ` @@ -82,15 +83,15 @@ All variables that are part of a `[...].config` object can be saved to non-volat ## Setting up sensorless The ODrive can run without encoder/hall feedback, but there is a minimum speed, usually around a few hunderd RPM. -To give an example, suppose you have a motor with 7 pole pairs, and you want to spin it at 3000 RPM. Then you would set the `input_vel` to `3000 * 2*pi/60 * 7 = 2199 rad/s electrical`. - -Below are some suggested starting parameters that you can use. Note that you _must_ set the `pm_flux_linkage` correctly for sensorless mode to work. +Below are some suggested starting parameters that you can use. Note that you _must_ set the `pm_flux_linkage` correctly for sensorless mode to work. Motor calibration and setup must also be completed before sensorless mode will work. ``` odrv0.axis0.controller.config.vel_gain = 0.01 odrv0.axis0.controller.config.vel_integrator_gain = 0.05 odrv0.axis0.controller.config.control_mode = 2 -odrv0.axis0.controller.input_vel = 400 +odrv0.axis0.controller.input_vel = 10 +odrv0.axis0.controller.config.vel_limit = +odrv0.axis0.motor.config.current_lim = 2 * odrv0.axis0.config.sensorless_ramp.current odrv0.axis0.motor.config.direction = 1 odrv0.axis0.sensorless_estimator.config.pm_flux_linkage = 5.51328895422 / ( * ) ``` diff --git a/docs/control.md b/docs/control.md index 0f00d05f..886d7772 100644 --- a/docs/control.md +++ b/docs/control.md @@ -29,6 +29,8 @@ voltage_integral += current_error * current_integrator_gain voltage_cmd = current_error * current_gain + voltage_integral (+ voltage_feedforward when we have motor model) ``` +Note: `current_gain` and `current_integrator_gain` are automatically set according to `motor.config.current_control_bandwidth` + For more detail refer to [controller.cpp](https://github.com/madcowswe/ODrive/blob/master/Firmware/MotorControl/controller.cpp#L86). ### Controller Details: diff --git a/docs/encoders.md b/docs/encoders.md index 06cfb12b..aa7346cd 100644 --- a/docs/encoders.md +++ b/docs/encoders.md @@ -43,7 +43,7 @@ That's it, now on every reboot the motor will turn in one direction until it fin * If your motor has problems reaching the index location due to the mechanical load, you can increase `.motor.config.calibration_current`. ### Reversing index search -Sometimes you would like the index search to only happen in a particular direction (the reverse of the default), instead of swapping the motor leads, you can ensure the following three values are negative: +Sometimes you would like the index search to only happen in a particular direction (the reverse of the default). Instead of swapping the motor leads, you can ensure that the following three values are negative: * `.config.calibration_lockin.vel` * `.config.calibration_lockin.accel` * `.config.calibration_lockin.ramp_distance` @@ -78,7 +78,7 @@ Do you still have no errors? Awesome. Now, setup the motor and encoder to use kn * `.encoder.config.pre_calibrated = True` * `.motor.config.pre_calibrated = True ` -And see if ODrive agrees that calibration worked by just running +And see if ODrive agrees that the calibration worked by just running * `.encoder.config.pre_calibrated` (using no "= True" ). Make sure that 'pre_calibrated' is in fact True. @@ -121,6 +121,7 @@ Connect to the I pin, see if you get a pulse on a complete rotation. Sometimes t If you are using SPI, use a logic analyzer and connect to the CLK, MISO, and CS pins. Set a trigger for the CS pin and ensure that the encoder position is being sent and is increasing/decreasing as you spin the motor. There is extremely cheap hardware that is supported by [Sigrok](https://sigrok.org/) for protocol analysis. + ## Encoder Noise Noise is found in all circuits, life is just about figuring out if it is preventing your system from working. Lots of users have no problems with noise interfering with their ODrive operation, others will tell you "_I've been using the same encoder as you with no problems_". Power to 'em, that may be true, but it doesn't mean it will work for you. If you are concerned about noise, there are several possible sources: @@ -128,13 +129,13 @@ Noise is found in all circuits, life is just about figuring out if it is prevent * Long wires between encoder and ODrive * Use of ribbon cable -The following _might_ mitigate noise problems. Use shielded cable, or use twisted pairs, where one side of each twisted pair is tied to ground, the other side is tied to your signal. If you are using SPI, use a 20-50 ohm resistor in series on CLK, which is more susceptible noise. +The following _might_ mitigate noise problems. Use shielded cable, or use twisted pairs, where one side of each twisted pair is tied to ground and the other side is tied to your signal. If you are using SPI, use a 20-50 ohm resistor in series on CLK, which is more susceptible noise. -If you are using an encoder with an index signal, another problem that has been encountered is with noise on the Z input of ODrive. Symptoms for this problem include: +If you are using an encoder with an index signal, another problem that has been encountered is noise on the Z input of ODrive. Symptoms for this problem include: * difficulty with requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE, where your calibration sequence may not complete * strange behavior after performing odrv0.save_configuration() and odrv0.reboot() * when performing an index_search, the motor does not return to the same position each time. -One easy step that _might_ fix the noise on the Z input has been to solder a 22nF-47nF capacitor to the Z pin and the GND pin on the underside of the ODrive board. +One easy step that _might_ fix the noise on the Z input is to solder a 22nF-47nF capacitor to the Z pin and the GND pin on the underside of the ODrive board. ## Hall feedback pinout If position accuracy is not a concern, you can use A/B/C hall effect encoders for position feedback. @@ -170,12 +171,16 @@ Apart from (incremental) quadrature encoders, ODrive also supports absolute SPI Some of these chips come with evaluation boards that can simplify mounting the chips to your motor. For our purposes if you are using an evaluation board you should select the settings for 3.3v. +**Note:** The AMT23x family has a hardware bug that causes them to not properly tristate the MISO line. To use them with ODrive, there are two workarounds. One is to sequence power to the encoder a second or two after the ODrive recieves power. This allows 1 encoder to be used without issue. Another solution is to add a tristate buffer, such as the 74AHC1G125SE, on the MISO line between the ODrive and each AMT23x encoder. Tie the enable pin on the buffer to the CS line for the respective encoder. This allows for more than one AMT23x encoder, or one AMT23x and another SPI encoder, to be used at the same time. + 1. Connect the encoder to the ODrive's SPI interface: - The encoder's SCK, MISO (aka "DATA" on CUI encoders), GND and 3.3V should connect to the ODrive pins with the same label. - The encoder's MOSI should be tied to 3.3V (AMS encoders only. CUI encoders don't have this pin.) - The encoder's Chip Select (aka nCS/CSn) can be connected to any of the ODrive's GPIOs (caution: GPIOs 1 and 2 are usually used by UART). +If you are having calibration problems, make sure that your magnet is centered on the axis of rotation on the motor. Some users report that this has a significant impact on calibration. Also make sure that your magnet height is within range of the spec sheet. + 2. In `odrivetool`, run: .encoder.config.abs_spi_cs_gpio_pin = 4 # or which ever GPIO pin you choose diff --git a/docs/getting-started.md b/docs/getting-started.md index 94bcf15e..eb4d49a0 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -19,6 +19,7 @@ permalink: / - [Other control modes](#other-control-modes) - [Watchdog Timer](#watchdog-timer) - [What's next?](#whats-next) +- [Upgrading from 0.4.12](#upgrading-from-0412) @@ -41,7 +42,7 @@ permalink: / -* A power supply (12V-24V for the 24V board variant, 12V-48V for the 48V board variant). A battery is also fine. Some advice on choosing a power supply can be found [here](https://things-in-motion.blogspot.com/2018/12/how-to-select-right-power-source-for.html). +* A power supply (12V-24V for the 24V board variant, 12V-56V for the 56V board variant). A battery is also fine. Some advice on choosing a power supply can be found [here](https://things-in-motion.blogspot.com/2018/12/how-to-select-right-power-source-for.html).
What voltage variant do I have?
On all ODrives shipped July 2018 or after have a silkscreen label clearly indicating the voltage variant. @@ -61,13 +62,13 @@ All non-power I/O is 3.3V output and 5V tolerant on input, on ODrive v3.3 and ne ### Wiring up the encoders Connect the encoder(s) to J4. The A,B phases are required, and the Z (index pulse) is optional. The A,B and Z lines have 3.3k pull up resistors, for use with open-drain encoder outputs. For single ended push-pull signals with weak drive current (\<4mA), you may want to desolder the pull-ups. -![Image of ODrive all hooked up](https://docs.google.com/drawings/d/e/2PACX-1vTCD0P40Cd-wvD7Fl8UYEaxp3_UL81oI4qUVqrrCJPi6tkJeSs2rsffIXQRpdu6rNZs6-2mRKKYtILG/pub?w=1716&h=1281) +![Image of ODrive all hooked up](https://docs.google.com/drawings/d/e/2PACX-1vTpJziAisrkvV1kTL4vckAJkmJ-BAvTwN1GeZZNCNwpTHv47Cf8bpz-gJqK2Z3un6FCHT4E-rcuUg6c/pub?w=1716&h=1281) ### Safety & Power UP
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/48 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. +* 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. ## Downloading and Installing Tools @@ -81,7 +82,7 @@ Most instructions in this guide refer to a utility called `odrivetool`, so you s 2. Launch the command prompt. * __Anaconda__: In the start menu, type `Anaconda Prompt` Enter * __Standalone Python__: In the start menu, type `cmd` Enter -3. Install the ODrive tools by typing `pip install odrive` Enter +3. Install the ODrive tools by typing `pip install --upgrade odrive` Enter 4. Plug in a USB cable into the microUSB connector on ODrive, and connect it to your PC. 5. Use the [Zadig](http://zadig.akeo.ie/) utility to set ODrive driver to libusb-win32. * Check 'List All Devices' from the options menu, and select 'ODrive 3.x Native Interface (Interface 2)'. With that selected in the device list choose 'libusb-win32' from the target driver list and then press the large 'install driver' button. @@ -107,13 +108,13 @@ brew install libusb ``` 5. Now that you have Python 3 and all the package managers, run: ```bash -pip3 install odrive +pip3 install --upgrade odrive ``` __Troubleshooting__ 1. Permission Errors: Just run the previous command in sudo ```bash -sudo pip3 install odrive +sudo pip3 install --upgrade odrive ``` 2. Dependency Errors: If the installer doesn't complete and you get a dependency @@ -126,7 +127,7 @@ Try step 5 again ### Linux 1. [Install Python 3](https://www.python.org/downloads/). (for example, on Ubuntu, `sudo apt install python3 python3-pip`) -2. Install the ODrive tools by opening a terminal and typing `sudo pip3 install odrive` Enter +2. Install the ODrive tools by opening a terminal and typing `sudo pip3 install --upgrade odrive` Enter * This should automatically add the udev rules. If this fails for some reason you can add them manually: ```bash echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="0d[0-9][0-9]", MODE="0666"' | sudo tee /etc/udev/rules.d/91-odrive.rules @@ -230,7 +231,7 @@ You can save all `.config` parameters to persistent memory so the ODrive remembe ## Position control of M0 -Let's get motor 0 up and running. The procedure for motor 1 is exactly the same, so feel free to substitute `axis0` wherever it says `axis0`. +Let's get motor 0 up and running. The procedure for motor 1 is exactly the same, so feel free to substitute `axis1` wherever it says `axis0`. 1. Type `odrv0.axis0.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE` Enter. After about 2 seconds should hear a beep. Then the motor will turn slowly in one direction for a few seconds, then back in the other direction. @@ -373,3 +374,7 @@ You can now: * See how you can improve the behavior during the startup procedure, like [bypassing encoder calibration](encoders.md#encoder-with-index-signal). If you have any issues or any questions please get in touch. The [ODrive Community](https://discourse.odriverobotics.com/) warmly welcomes you. + + +## Upgrading from 0.4.12 +A new version (0.5.1) of ODrive firmware has released, complete with a new odrivetool. Follow the installation instructions, making sure to add the `--upgrade` flag to pip commands, and check out the [Changelog](../CHANGELOG.md) for changes! diff --git a/docs/hoverboard.md b/docs/hoverboard.md index a743a21c..cee67d50 100644 --- a/docs/hoverboard.md +++ b/docs/hoverboard.md @@ -98,7 +98,7 @@ Check the status of the encoder object: odrv0.axis0.encoder ``` -Check that there are no errors. If your hall sensors has a standard timing angle then `offset_float` should be close to 0.5. +Check that there are no errors. If your hall sensors has a standard timing angle then `offset_float` should be close to 0.5 or 1.5. ```txt error = 0x0000 (int) offset_float = 0.5126956701278687 (float) diff --git a/docs/interfaces.md b/docs/interfaces.md index 28a1b3dc..0b9cb6c3 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -18,25 +18,25 @@ The ODrive can be controlled over various ports and protocols. If you're comfort ## Pinout -| # | Label | `GPIO_MODE_DIGITAL` | `GPIO_MODE_ANALOG_IN` | `GPIO_MODE_UART0` | `GPIO_MODE_PWM0` | `GPIO_MODE_CAN0` | `GPIO_MODE_I2C0` | `GPIO_MODE_ENC0` | `GPIO_MODE_ENC1` | -|----|---------------|------------------------|-----------------------|-------------------|------------------|------------------|------------------|------------------|------------------| -| 0 | _not a pin_ | | | | | | | | | -| 1 | GPIO1 (+) | general purpose | analog input | **UART0.TX** | PWM0.0 | | | | | -| 2 | GPIO2 (+) | general purpose | analog input | **UART0.RX** | PWM0.1 | | | | | -| 3 | GPIO3 | general purpose | **analog input** | | PWM0.2 | | | | | -| 4 | GPIO4 | general purpose | **analog input** | | PWM0.3 | | | | | -| 5 | GPIO5 | general purpose | **analog input** (*) | | | | | | | -| 6 | GPIO6 (*) (+) | **general purpose** | | | | | | | | -| 7 | GPIO7 (*) (+) | **general purpose** | | | | | | | | -| 8 | GPIO8 (*) (+) | **general purpose** | | | | | | | | -| 9 | M0.A | general purpose | | | | | | **ENC0.A** | | -| 10 | M0.B | general purpose | | | | | | **ENC0.B** | | -| 11 | M0.Z | **general purpose** | | | | | | | | -| 12 | M1.A | general purpose | | | | | I2C.SCL | | **ENC1.A** | -| 13 | M1.B | general purpose | | | | | I2C.SDA | | **ENC1.B** | -| 14 | M1.Z | **general purpose** | | | | | | | | -| 15 | _not exposed_ | general purpose | | | | **CAN0.RX** | I2C.SCL | | | -| 16 | _not exposed_ | general purpose | | | | **CAN0.TX** | I2C.SDA | | | +| # | Label | `GPIO_MODE_DIGITAL` | `GPIO_MODE_ANALOG_IN` | `GPIO_MODE_UART0` | `GPIO_MODE_PWM0` | `GPIO_MODE_CAN0` | `GPIO_MODE_I2C0` | `GPIO_MODE_ENC0` | `GPIO_MODE_ENC1` | `GPIO_MODE_MECH_BRAKE` | +|----|---------------|------------------------|-----------------------|-------------------|------------------|------------------|------------------|------------------|------------------|------------------------| +| 0 | _not a pin_ | | | | | | | | | | +| 1 | GPIO1 (+) | general purpose | analog input | **UART0.TX** | PWM0.0 | | | | | mechanical brake | +| 2 | GPIO2 (+) | general purpose | analog input | **UART0.RX** | PWM0.1 | | | | | mechanical brake | +| 3 | GPIO3 | general purpose | **analog input** | | PWM0.2 | | | | | mechanical brake | +| 4 | GPIO4 | general purpose | **analog input** | | PWM0.3 | | | | | mechanical brake | +| 5 | GPIO5 | general purpose | **analog input** (*) | | | | | | | mechanical brake | +| 6 | GPIO6 (*) (+) | **general purpose** | | | | | | | | mechanical brake | +| 7 | GPIO7 (*) (+) | **general purpose** | | | | | | | | mechanical brake | +| 8 | GPIO8 (*) (+) | **general purpose** | | | | | | | | mechanical brake | +| 9 | M0.A | general purpose | | | | | | **ENC0.A** | | | +| 10 | M0.B | general purpose | | | | | | **ENC0.B** | | | +| 11 | M0.Z | **general purpose** | | | | | | | | | +| 12 | M1.A | general purpose | | | | | I2C.SCL | | **ENC1.A** | | +| 13 | M1.B | general purpose | | | | | I2C.SDA | | **ENC1.B** | | +| 14 | M1.Z | **general purpose** | | | | | | | | | +| 15 | _not exposed_ | general purpose | | | | **CAN0.RX** | I2C.SCL | | | | +| 16 | _not exposed_ | general purpose | | | | **CAN0.TX** | I2C.SDA | | | | (*) ODrive v3.5 and later
diff --git a/docs/mechanical-brakes.md b/docs/mechanical-brakes.md new file mode 100644 index 00000000..ab2164e1 --- /dev/null +++ b/docs/mechanical-brakes.md @@ -0,0 +1,61 @@ +# Mechanical Brake + +Some systems employ mechanical brakes on motors as a safety feature. These brakes can also be engaged as a power-saving function if the motor is not moving, but still under load. + +ODrive supports the use of its GPIO pins to connect to external brake drive electronics. + +When the ODrive engages the drive electronics, the brake will be disabled. When the drive enters a fault or idle state, the brake will be re-engaged. + +--- + +## Mechanical Brake Configuration +Each axis supports one mechanical brake. The following properties are accessible through `odrivetool`: + +Name | Type | Default +--- | -- | -- +gpio_num | int | 0 +is_active_low | boolean | true + +### gpio_num +The GPIO pin number, according to the silkscreen labels on ODrive. Set with these commands: +``` +..mechanical_brake.config.gpio_num = <1, 2, 3, 4, 5, 6, 7, 8> +``` +After GPIO pin number is changed, you'll need to run `.save_configuration()` and `.reboot()` for changes to take effect. + +### is_active_low +Most safety braking systems are active low, e.g. when the power is off, the brake is on. If the system uses brake drive electronics which use active high logic, flip this bit then reconsider the safety implications of your design... + +### Enabling +The configuration of the mechanical brake will enable the brake functionality. There's no need to specifically 'enable' this feature. + + +### Example + +Let's say we're hacking away on an old ABB robotic arm. We've wired a 24V brake drive circuit triggered by GPIO5. When GPIO5 is driven, it will release the brakes on the axis we're moving. + +We need to notify the axis of the GPIO number we've attached our brake to, and configure the ODrive pin mode to `GPIO_MODE_MECH_BRAKE`: +``` +..mechanical_brake.config.gpio_num = 5 +.config.gpio5_mode = GPIO_MODE_MECH_BRAKE +``` + +Pin configurations only take effect after a save/reboot so don't forget to run: +``` +.save_configuration() +.reboot() +``` + +### Testing The Mechanical Brakes +Depending on your system this could be a dangerous experiment. Ensure that you have taken all necessary precautions to confirm if the wrong brake were inadvertently released it would not lead to injury or damage to equipment. + +``` +..mechanical_brake.release() +``` +Note: If a brake is configured, it will be automatically engaged/disengaged during the next state machine step. + +After you're satisfied with the testing, you can re-enable the brake using the command + +``` +..mechanical_brake.engage() +``` diff --git a/docs/odrivetool.md b/docs/odrivetool.md index 3cd01972..352f6dc4 100644 --- a/docs/odrivetool.md +++ b/docs/odrivetool.md @@ -85,11 +85,12 @@ To compile firmware from source, refer to the [developer guide](developer-guide) ### Troubleshooting * __Windows__: During the update, a new device called "STM32 BOOTLOADER" will appear. Open the [Zadig utility](http://zadig.akeo.ie/) and set the driver for "STM32 BOOTLOADER" to libusb-win32. After that the firmware update will continue. +* __Linux__: Try running `sudo odrivetool dfu` instead of `odrivetool dfu`. * On some machines you will need to unplug and plug back in the USB cable to make the PC understand that we switched from regular mode to bootloader mode. * If the DFU script can't find the device, try forcing it into DFU mode.
How to force DFU mode (ODrive v3.5 and newer)
- Flick the DIP switch that "DFU, RUN" to "DFU" and power cycle the board. After you're done upgrading firmware, don't forget to put the switch back into the "RUN" position and power cycle the board again. + Flick the DIP switch that says "DFU, RUN" to "DFU" and power cycle the board. If that alone doesn't work, also connect the pin "GPIO6" to "GND". After you're done upgrading firmware, don't forget to put the switch back into the "RUN" position and power cycle the board again.
How to force DFU mode (ODrive v3.1, v3.2)
diff --git a/tools/odrive/dfu.py b/tools/odrive/dfu.py index bfeb0a81..561ed776 100755 --- a/tools/odrive/dfu.py +++ b/tools/odrive/dfu.py @@ -223,9 +223,12 @@ def put_into_dfu_mode(device, cancellation_token): Puts the specified device into DFU mode """ if not hasattr(device, "enter_dfu_mode"): - print("The firmware on device {} does not support DFU. You need to \n" - "flash the firmware once using STLink (`make flash`), after that \n" - "DFU with this script should work fine." + print("The firmware on device {} cannot soft enter DFU mode.\n" + "Please remove power, put the DFU switch into DFU mode,\n" + "then apply power again. Then try again.\n" + "If it still doesn't work, you can try to use the DeFuse app or \n" + "dfu-util, see the odrive documentation.\n" + "You can also flash the firmware using STLink (`make flash`)" .format(device.__channel__.usb_device.serial_number)) return diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index cff886bd..ea11aa8e 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -18,6 +18,7 @@ GPIO_MODE_PWM0 = 10 GPIO_MODE_ENC0 = 11 GPIO_MODE_ENC1 = 12 GPIO_MODE_ENC2 = 13 +GPIO_MODE_MECH_BRAKE = 14 # ODrive.Can.Protocol PROTOCOL_SIMPLE = 0 diff --git a/tools/odrive/tests/run_all_tests.sh b/tools/odrive/tests/run_all_tests.sh new file mode 100755 index 00000000..ab04a99c --- /dev/null +++ b/tools/odrive/tests/run_all_tests.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +declare -a tests=('analog_input_test.py' + 'calibration_test.py' + 'can_test.py' + 'closed_loop_test.py' + 'encoder_test.py' + 'fibre_test.py' + 'integration_test.py' + 'nvm_test.py' + 'pwm_input_test.py' + 'step_dir_test.py' + 'uart_ascii_test.py' + ) +summary="" + +for test in "${tests[@]}"; do + (ipython3 "$test" -- --test-rig-yaml ../../test-rig-rpi.yaml || true) | tee /tmp/odrivetest.log + if grep "All tests passed!" /tmp/odrivetest.log; then + summary="$summary - $test: passed"$'\n' + else + summary="$summary - $test: failed"$'\n' + fi + + echo "########################" + echo "Current status:" + echo -n "$summary" + echo "########################" +done