[0.6.3] - 2022-08-19


  • Support for AMT21 self-sending mode (amt21_encoder_group0.config.event_driven_mode). This fixes aliasing problems that occurred due to different internal update frequencies between the AMT21 and ODrive. AMT21 firmware needs to be updated to use this mode.


  • Fix voltage feedforward terms (omega*L and backEMF). They were too low by a factor of 2*pi. This only has an effect if wL_FF_enable or bEMF_FF_enable in <axis>.config.motor was true. Fixes a regression in 0.6.0.

  • Fixed bug in input modes InputMode.POS_FILTER and InputMode.TRAP_TRAJ whereby the axis would snap to position 0 as soon as entering CLOSED_LOOP_CONTROL, and then move back to the position at which it was armed. With this fix, the axis does not move when armed. Fixes a regression in 0.5.2.

  • Fixed lockin spin (used for encoder offset calibration and other calibration states) for MotorType.GIMBAL. Previously the motor would not move when trying to do a lockin spin in gimbal motor mode. Fixes a regression in 0.6.1.

  • [ODrive S1] improved accuracy of motor calibration


  • All motor modes now include the feed forward term V_dq += I_dq * phase_resistance where I_dq is the current setpoint.

    • Gimbal motor mode no longer reinterprets current as voltage, but requires that phase_resistance and phase_resistance_valid are set. Set phase_resistance = 1 to emulate old behavior.

  • Reboot no longer needed to enabled/reconfigure AMT21

API Migration Guide

  • <axis>.config.motor.phase_resistance_inductance_valid replaced by phase_resistance_valid and phase_inductance_valid

  • <axis>.config.motor.R_wL_FF_enable renamed to wL_FF_enable because R is always fed forward

[0.6.2] - 2022-08-09


  • <axis>.controller.effective_torque_setpoint to monitor effective torque commanded by controller

  • <axis>.config.motor.current_slew_rate_limit. Increase to get faster torque response, reduce to improve current controller stability. Anti-windup connection to pos/vel controller not yet implemented, be careful of using very slow rates.

  • Calculated brake resistor current at <odrv>.brake_resistor0.current

  • inc_encoder0.raw, inc_encoder1.raw and ams21_encoder_group0.raw for better debugging insight

  • <axis>.config.startup_max_wait_for_ready (defaults to 3 seconds)

  • <spi_encoder>.n_errors for better diagnostics

  • [odrivetool] Add --version option to odrivetool dfu


  • reboot on erase_configuration() even if NVM was already clean to ensure reset of in-RAM config

  • Apply modulation limit also for voltage-only control. This fixes occasional ODriveError.SYSTEM_LEVEL if gimbal motor mode was used.

  • Clear <odrv>.procedure_result on <odrv>.clear_errors()

  • Update <odrv>.ibus and check for violations of dc_max_positive_current and dc_max_negative_current also when brake resistor is unavailable or disabled (fixes a regression in 0.6.1)

  • Fix Trapezoidal Trajectory mode not working (fixes a regression in 0.6.0)

  • Fix startup actions not working (e.g. <axis>.config.startup_motor_calibration). When these flags were enabled, the device started with a ODriveError.DRV_FAULT instead of doing the startup action.

  • Autoclear error flag on AMS encoders. Before, as soon as the AMS encoder detected any transfer error (such as bad parity), the ODrive would stop accepting values from it until the encoder was power cycled. With this fix, the ODrive will still drop out of closed loop control if the error flag is set, but it can re-enter closed loop control right after that.

  • Increase current limit factor during motor resistance calibration from 1.5 to 2.0 (times <axis>.config.motor.calibration_current). This reduces the likelihood of ODriveError.CURRENT_LIMIT_VIOLATION during motor calibration (especially if there’s a twitch motion of the motor).

  • Fix scaling of hall and sensorless mode, reported speed / position was too high by a factor of motor.pole_pairs

    • Note: Due to this fix, position control may drift over time in hall mode (sensorless does not support position control mode)

  • Report missing pos_setpoint / vel_setpoint / torque_setpoint in <axis>.active_errors as ODriveError.MISSING_INPUT. This allows for error-free startup if PWM input becomes available later than the ODrive’s initialization time.

  • [odrivetool] fix enum decoding compatibility with firmware 0.5.4

  • [odrivetool] Added back missing old-style enum names (such as ENCODER_MODE_HALL, used in some versions of the docs and corresponding to EncoderMode.HALL)

  • [odrivetool] Compatibility with Python 3.6

  • [odrivetool] Added missing enum value ODriveError.CALIBRATION_ERROR (fixes UNKNOWN ERROR: 0x40000000)

  • [odrivetool] Fix corruption of Python interpreter. When __pycache__ was clear (e.g. on first load of the odrive module), on ODrive discovery the backend would overwrite Python’s b'\0' buffer with something non-zero, resulting in subsequent unexpected behavior such as print(b'\0') => ':'.


  • set_abs_pos moved from mapper to axis level and now compensates input_pos and pos_setpoint when changing the absolute position reference.

  • Changes to <axis>.controller.config.absolute_setpoints takes effect without reboot (in IDLE)

  • CANSimple message Set Linear Count that took integer counts changed to Set Absolute Position that takes a float.

  • Changed CAN heartbeat from reporting active_errors to reporting active_errors | disarm_reason

  • Changed current_status size in CAN heartbeat from 32 bit to 8 bit

  • Added disarm_reason to CAN message MSG_GET_ERROR (0x003)

API Migration Guide

  • <axis>.pos_vel_mapper.set_abs_pos() moved to <axis>.set_abs_pos().

  • <axis>.controller.config.enable_current_mode_vel_limit moved back to <axis>.controller.config.enable_torque_mode_vel_limit (fixes a regression in 0.6.0).

Known Issues

  • Whenever entering AxisState.CLOSED_LOOP_CONTROL with an input mode of InputMode.TRAP_TRAJ or InputMode.POS_FILTER, the axis snaps to position 0 and then moves back to the position at which it was armed. Using trapezoidal trajectory or input position filter mode is therefore discouraged on this firmware version.

  • Position control may drift over time in hall mode

  • Lockin spin (used for encoder offset calibration and other calibration states) with motor_type = MotorType.GIMBAL tries to use current control instead of open loop voltage control. This means depending on configuration, lockin spin either exits with a configuration error or just doesn’t move the motor. As a workaround, in <axis>.config.motor set phase_inductance = 1, phase_resistance_inductance_valid = True and current_control_bandwidth = 0.

[0.6.1] - 2022-06-03


  • Improved error reporting

  • Encoder calibration no longer uses <axis>.config.motor.calibration_current as current but rather <axis>.config.calibration_lockin.current

  • Fixed unknown command on first ASCII protocol command after startup caused by a spurious byte in the RX buffer

  • Possibly fixed occasional UART TX hang that would occur during closed loop control

  • Fixed winusb.sys not automatically loading

  • Fixed various USB enumeration errors (string descriptors showing as ‘Љ’, ‘(error)’ or in wrong places and errors like “could not read langid”, “couldn’t open device” and “not available”)

  • Restored ability to disable CAN Simple messages by setting the interval to 0 (regression in 0.6.0)

  • Fixed wrong velocity used during second half of hall polarity calibration

  • Fixed homing getting stuck in ProcedureResult.BUSY

  • Fixed AxisState.FULL_CALIBRATION_SEQUENCE when using onboard encoder

  • Fixed FET temperature readings on ODrive Pro. The reported temperature was higher than the actual temperature:

    • reported 100°C => actual 103°C

    • reported 40°C => actual 29°C

    • actual = old_reported * 0.92 - 7.79

  • Fixed CAN bug where the ODrive would stop handling incoming messages after a large number of incoming messages.

  • Fixed PWM input on ODrive Pro

  • Increased default velocity limit from 2 turns/s to 10 turns/s. This also increases the default absolute velocity margin, leading to less overspeed errors on first use.

  • Decreased cutoff frequency of incremental encoder input filters


  • Landing page popup when ODrive is plugged in while Chrome browser is open

  • <axis>.config.index_search_at_target_vel_only option to make index search sensitive straight away or if it needs to get to target speed first.

  • SpiEncoder.delay to compensate for slow SPI encoders

  • Exposed more hall effect sensor state at HallEncoder.raw_hall_state, HallEncoder.Config.hall_polarity and HallEncoder.Config.hall_polarity_calibrated

  • Diagnostics variable <odrv>.can.n_restarts (a CAN restart isn’t necessarily an error - if the device is not on any bus the CAN interface will restart continuously)

  • Id,q feedforward terms <axis>.motor.input_id, <axis>.motor.input_iq

  • More tweak options in <axis>.config.motor: ff_pm_flux_linkage, torque_model_l_d, torque_model_l_q (see API reference for details)

  • Add timeout for calibration functions (e.g. when target current can’t be reached because it’s above the current limit)

  • CAN Simple protocol command MSG_GET_TEMPERATURE

  • CAN RX count (for debugging): <odrv>.can.n_rx


  • Increased maximum modulation depth from 80% to 100% (that means more max velocity for the same DC voltage).

  • Most variables in axis.config.motor require the axis to be in idle state to take effect.

  • Motor calibration is persistent by default on next save_configuration() (previously it required setting pre_calibrated)

  • At startup, the axis previously waited in UNDEFINED state for 2 seconds to wait for initialization to complete. Now it goes into IDLE state immediately. Use (axis.active_errors & ODriveError.INITIALIZING) == 0 to check for readiness.

  • Requesting an unknown state drops to IDLE without setting any error

  • <axis>.acim_estimator units changed

  • New LED behavior: NOT READY: Blue, READY: Cyan, RUNNING: Green, ERROR: Red

  • Removed CAN message MSG_GET_SENSORLESS_ERROR (0x005)

  • Changed CAN message 0x003 from MSG_GET_MOTOR_ERROR to MSG_GET_ERROR

API Migration Guide

  • <odrv>.error removed. Use <axis>.active_errors and <axis>.disarm_reason instead.

  • <odrv>.n_issues removed. use <odrv>.issues.length instead.

  • <odrv>.config.error_gpio_pin moved to <axis>.config.error_gpio_pin

  • <axis>.motor.config moved to <axis>.config.motor

  • <axis>.motor.config.current_lim moved to <axis>.config.motor.current_soft_max and <odrv>.config.inverter0.current_soft_max.

  • <axis>.motor.config.current_lim_margin removed. Use <axis>.config.motor.current_hard_max and instead. A separate limit for the inverter was added under <odrv>.config.inverter0.current_hard_max and should usually not be modified.

  • <axis>.motor.config.pre_calibrated and <axis>.motor.is_calibrated replaced by <axis>.config.motor.phase_resistance_inductance_valid. This new variable goes to True automatically after successful motor calibration.

  • <axis>.motor.config.torque_lim replaced by <axis>.config.torque_soft_min and <axis>.config.torque_soft_max.

  • <axis>.motor.config.requested_current_range removed. It now gets internally set to 1.1 * min(<odrv>.config.inverter0.current_hard_max, <axis>.config.motor.current_hard_max).

  • <axis>.motor.current_control moved to <axis>.foc

  • <axis>.motor.is_armed moved to <axis>.is_armed

  • <axis>.motor.I_bus_hard_min and <axis>.motor.I_bus_hard_max moved to <axis>.I_bus_hard_min and <axis>.I_bus_hard_max

  • <axis>.motor.dc_calib_tau removed

  • <axis>.motor.last_error_time removed. Use <axis>.disarm_time instead.

  • <axis>.sensorless_estimator.config.observer_gain moved to <axis>.config.motor.sensorless_observer_gain

  • <axis>.sensorless_estimator.config.pll_bandwidth moved to <axis>.config.motor.sensorless_pll_bandwidth

  • <axis>.sensorless_estimator.config.pm_flux_linkage moved to <axis>.config.motor.sensorless_pm_flux_linkage and made optional by <axis>.config.motor.sensorless_pm_flux_linkage_valid (by default implied by <axis>.config.motor.pole_pairs and <axis>.config.motor.torque_constant)

  • Reorganized brake resistor API. On ODrive S1 all brake-resistor related variables got grouped together. On ODrive Pro (which supports no brake resistor) they got removed.

    • <odrv>.brake_resistor_armed moved to <odrv>.brake_resistor0.is_armed or removed

    • <odrv>.brake_resistor_saturated moved to <odrv>.brake_resistor0.is_saturated or removed

    • <odrv>.config.enable_brake_resistor moved to <odrv>.config.brake_resistor0.enable or removed

    • <odrv>.config.brake_resistance moved to <odrv>.config.brake_resistor0.resistance or removed

    • <odrv>.config.enable_dc_bus_overvoltage_ramp moved to <odrv>.config.brake_resistor0.enable_dc_bus_overvoltage_ramp or removed

    • <odrv>.config.dc_bus_overvoltage_ramp_start moved to <odrv>.config.brake_resistor0.dc_bus_overvoltage_ramp_start or removed

    • <odrv>.config.dc_bus_overvoltage_ramp_end moved to <odrv>.config.brake_resistor0.dc_bus_overvoltage_ramp_end or removed

  • <axis>.controller.status, <axis>.motor.error and <axis>.error removed. Replaced by axis.active_errors and axis.disarm_reason.

[0.6.0] - 2022-02-21


  • Fixed CAN command MSG_GET_IQ to return Iq setpoint and Iq measurement as documented

  • Fixed SPI hang during high CPU load

  • Make current limit active in lockin spin

  • Fixed violation of velocity ramp limit when increasing the velocity limit in InputMode.VEL_RAMP mode while input_vel is higher than the old velocity limit


  • Added support for ODrive Pro

  • Added motor.resistance_calibration_I_beta for debugging of MOTOR_UNBALANCED_PHASES error

  • Added bursty sine stimulus (InputMode.TUNING)

  • issues count n_issues to indicated unexpected issues (probably bugs)

  • Added controller.config.use_commutation_vel

  • Added 8kHz logging (“oscilloscope”) of arbitrary memory locations (internal use only)

  • Added firmware commit hash hex(<odrv>.commit_hash)

  • Make DRV config parameters configurable (internal use only)

  • odrivetool: Added experimental machine-centric configuration system. See example_config, status(example_config), apply(example_config) and calibrate(example_config).


  • Changing the CAN node ID now requires a reboot to take effect.

  • Reboot on hard fault (n_issues will init to 1 after reboot)

  • Anticogging temporarily not available

  • Encoder object split into multiple objects (hardware-dependent components, estimator, interpolator, commutation mapper and load mapper). See API Migration Notes below.

  • Hall effect innovation residual size can be non-uniform

  • when an illegal hall state is encountered, rather than using the last hall state to derive a measurement residual, the measurement residual is returned as 0

  • Hall calibration runs in both directions

  • On transient SPI errors, the encoder estimator is left in prediction mode rather than correcting towards the last known measurement

  • SPI error rate low pass filter gain increased from 1 to 50 so the error clears faster (~100ms) when the encoder is connected

  • Snap-to-zero-velocity is no longer supported for SPI encoders because it was not sensible for most SPI encoders and it saves the user from specifying CPR for SPI encoders.

  • Interpolator is not limited to equisized steps

  • Encoder offset calibration only checks at the end if the index was found, so offset calibration and index search can happen in one.

  • Encoder offset calibration uses least squares line fit

  • When absolute setpoints are used (controller.config.absolute_setpoints), position control is not allowed before homing.

  • The CAN messages MSG_GET_ENCODER_ERROR (0x004), MSG_GET_ENCODER_COUNT (0x00a) and MSG_GET_SENSORLESS_ESTIMATES (0x015) got removed

  • Allow responses on CAN Simple if DLC == 0

  • odrivetool: Changed enum naming convention from AXIS_STATE_CLOSED_LOOP_CONTROL to AxisState.CLOSED_LOOP_CONTROL

API Migration Notes

  • <axis>.encoder was removed

    • Instead of <axis>.encoder.config.mode use <axis>.config.load_encoder and <axis>.config.commutation_encoder. To set the SPI encoder type, use <odrv>.spi_encoderX.config.mode.

    • Instead of <axis>.encoder.pos_estimate/<axis>.encoder.pos_circular use <axis>.load_mapper.pos_rel or <axis>.load_mapper.pos_abs and make sure <axis>.controller.config.circular_setpoints is set correctly.

    • Instead of <axis>.encoder.vel_estimate use <axis>.load_mapper.vel.

    • Instead of <axis>.encoder.set_linear_count() use <axis>.load_mapper.set_abs_pos().

    • Instead of <axis>.encoder.config.bandwidth use <axis>.config.encoder_bandwidth.

    • Encoder type specific variables and config can be accessed via <odrv>.inc_encoder0, <odrv>.inc_encoder1, <odrv>.spi_encoder0, <odrv>.spi_encoder1, <odrv>.hall_encoder0, <odrv>.hall_encoder1.

    • <axis>.encoder.config.calib_range moved to <axis>.config.calib_range

    • <axis>.encoder.config.calib_scan_distance moved to <axis>.config.calib_scan_distance

    • <axis>.encoder.config.calib_scan_omega replaced by <axis>.config.calib_scan_vel (changed units)

    • Instead of <axis>.encoder.error use <odrv>.spi_encoder0.status (or similar) for runtime (active) errors and <axis>.procedure_result after calibration for calibration errors.

  • <axis>.config.general_lockin.finish_on_enc_idx was removed without replacement.

  • Motor error codes that can only occur during motor calibration were removed. Inspect <axis>.procedure_result instead.

  • <axis>.controller.error was replaced by <axis>.controller.status which has a different type

  • <axis>.controller.anticogging_valid was removed without replacement.

  • <axis>.controller.load_encoder_axis was removed. Use <axis>.config.load_encoder instead.

  • <axis>.controller.last_error_time was removed. Use <axis>.disarm_time instead.

  • <axis>.sensorless_estimator.vel_estimate was removed. Use <axis>.sensorless_estimator.phase_vel instead (different unit).

  • <axis>.controller.config.enable_torque_mode_vel_limit moved to <axis>.controller.config.enable_current_mode_vel_limit (regression - reverted in 0.6.2).

[0.5.4] and older

See here for older releases.