Pre-calibrated Motor + Encoder Issue on ODrive v3.6 (0.5.6) with Hoverboard Motor & AS5047P

Hi
I have an ODrive v3.6 (56V) running firmware 0.5.6, controlling two hoverboard BLDC motor (15 pole pairs) with an AS5047P magnetic encoder in ABI mode.

Problem is

  • FULL_CALIBRATION_SEQUENCE` completes successfully with all errors = 0x0
  • Motor moves correctly in CLOSED_LOOP_CONTROL after calibration
  • After setting motor.pre_calibrated = True and encoder.pre_calibrated = True, then rebooting, the axis faults with:
  • Axis error: 0x40 (ERROR_MOTOR_FAILED)
  • Motor error: 0x4000000
  • Encoder error: 0x0

Is this a known issue with incremental encoders on 0.5.6? Should I use a different pre-calibration strategy, or is calibration-on-every-boot the only reliable option?

Hardware Setup

ODrive: v3.6-56V (firmware v0.5.6)
Motor: Hoverboard BLDC (~15 pole pairs, 5-10Ω)
Encoder: AS5047P (ABI mode, CPR = 4096)
Power supply: 50V, 30A capable
Brake resistor: wired


## Configuration Code (Working - No Pre-Cal)

python

import odrive
from odrive.enums import *
import time

odrv0 = odrive.find_any()
axis = odrv0.axis0

Clear and reset

odrv0.clear_errors()
axis.requested_state = AXIS_STATE_IDLE
time.sleep(0.5)

— MOTOR CONFIG —

axis.motor.config.motor_type = 0 # HIGH CURRENT
axis.motor.config.pole_pairs = 15
axis.motor.config.calibration_current = 10
axis.motor.config.current_lim = 15
axis.motor.config.requested_current_range = 25
axis.motor.config.resistance_calib_max_voltage = 25
axis.motor.config.current_control_bandwidth = 100

— ENCODER CONFIG —

axis.encoder.config.mode = ENCODER_MODE_INCREMENTAL
axis.encoder.config.cpr = 4096 # Measured via shadow_count test: -4107 ≈ 4096
axis.encoder.config.use_index = False
axis.encoder.config.pre_calibrated = False # THIS IS KEY - leaving false
axis.encoder.config.bandwidth = 1000
axis.encoder.config.calib_range = 0.05

— CONTROLLER CONFIG —

axis.controller.config.control_mode = CONTROL_MODE_POSITION_CONTROL
axis.controller.config.input_mode = INPUT_MODE_POS_FILTER
axis.controller.config.pos_gain = 20
axis.controller.config.vel_gain = 0.02
axis.controller.config.vel_integrator_gain = 0.05
axis.controller.config.vel_limit = 10

— BOARD CONFIG —

odrv0.config.enable_brake_resistor = False

print(“Saving base configuration…”)
odrv0.save_configuration()
time.sleep(5)

Reconnect after reboot

odrv0 = odrive.find_any()
axis = odrv0.axis0

— CALIBRATION —

print(“Running FULL_CALIBRATION_SEQUENCE…”)
odrv0.clear_errors()
axis.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE
while axis.current_state != AXIS_STATE_IDLE:
time.sleep(0.1)

print(f"Axis error: {hex(axis.error)}“)
print(f"Motor error: {hex(axis.motor.error)}”)
print(f"Encoder error: {hex(axis.encoder.error)}")

Output: All 0x0 ✓

— ENTER CLOSED LOOP —

axis.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
time.sleep(1)

Test motion

axis.controller.input_pos = 2
time.sleep(2)
axis.controller.input_pos = 0

Motor moves correctly ✓


## Configuration Code (Fails - With Pre-Cal)

python

After the above calibration succeeds, add this:

axis.motor.config.pre_calibrated = True
axis.encoder.config.pre_calibrated = True

axis.config.startup_motor_calibration = False
axis.config.startup_encoder_offset_calibration = False
axis.config.startup_closed_loop_control = True

print(“Saving pre-calibrated config…”)
odrv0.save_configuration()
time.sleep(5)

Reboot ODrive manually or reconnect

Now on reconnect:

odrv0 = odrive.find_any()
axis = odrv0.axis0

print(f"Axis state: {axis.current_state}“)
print(f"Axis error: {hex(axis.error)}”)
print(f"Motor error: {hex(axis.motor.error)}“)
print(f"Encoder error: {hex(axis.encoder.error)}”)

Output:

Axis state: 1 (IDLE)

Axis error: 0x40 (ERROR_MOTOR_FAILED)

Motor error: 0x4000000

Encoder error: 0x0


## Measurements & Debug Info

Encoder CPR verification (shadow_count test):

* **1 mechanical turn:** -4107 counts
* **5 mechanical turns:** -20,076 counts → -4015.2 counts/rev
* **Conclusion:** Actual CPR ≈ 4096 ✓ (matches configured value)

Bus voltage: 50.47V ✓
Encoder is counting cleanly ✓

## Questions

1. Is the `pre_calibrated` approach fundamentally incompatible with incremental encoders on firmware 0.5.6?
2. Should I stick with "calibrate at every startup" for reliability? This will be technically a problem .
3. Are there any known workarounds (e.g., specific current limits, gain adjustments) to make pre-calibration work?
4. Would switching the AS5047P to **SPI absolute mode** (`ENCODER_MODE_SPI_ABS_AMS`) solve this without needing pre-calibration?
Best regards

Shaker

Because incremental encoders cannot track the position of the motor between reboots, when using an incremental encoder you must either calibrate or do an index search on each startup. Usually index search is recommended, as it’s faster and more robust to load, however it still requires motor movement. You could run the AS5047 in SPI mode instead, however you’d have to change your wiring, and SPI can have some EMI issues with v3.6, so you may need to add ferrite rings to the motor phases or do other EMI mitigation strategies such as routing the SPI lines and motor phases separately, or shielding one/both.

Hello,

I am commissioning one hoverboard motor for a 6DOF simulator actuator. I would appreciate help understanding an overspeed fault before doing further powered tests.

My hardware:

  • ODrive v3.6 56V, firmware 0.5.6
  • Approximately 38.5V battery
  • Hoverboard motor, 15 pole pairs
  • AS5047P in SPI absolute mode
  • Encoder CPR: 16384
  • Encoder direction: +1
  • Encoder SPI error rate: always zero
  • 2Ω brake resistor installed
  • Motor mechanically unloaded except for a 1:1 belt connecting it to the encoder
  • Second ODrive axis disconnected and insulated

Motor calibration completes successfully:

  • Phase resistance: approximately 0.221Ω
  • Phase inductance: approximately 325µH
  • Torque constant: 0.516875 Nm/A
  • Current-control bandwidth: 100 rad/s
  • Motor and encoder pre-calibration survive reboot correctly

Closed-loop position holding works without errors. A low-energy torque command also produces movement. Positive movement is physically anticlockwise on my installation.

For the most recent velocity test, I used temporary RAM settings only:

Control mode: velocity control
Input mode: velocity ramp
Velocity command: +0.020 turns/s
Velocity ramp rate: 0.020 turns/s²
Velocity gain: 15
Velocity integrator gain: 15
Velocity integrator torque limit: 0.15 Nm
Torque limit: 0.45 Nm
Current limit: 1.0 A
Encoder bandwidth: 100 rad/s
Velocity limit: 0.030 turns/s
Velocity limit tolerance: 1.2
Overspeed threshold: 0.036 turns/s

The motor entered closed-loop control and accepted the command, but approximately 1.45 seconds later it stopped with:

Axis error:       0xA40
Motor error:      0x110000000
Encoder error:    0x0
Controller error: 0x1

My understanding of these errors is:

Controller 0x1 = OVERSPEED
Axis 0xA40 = CONTROLLER_FAILED + MOTOR_FAILED + WATCHDOG_TIMER_EXPIRED
Motor 0x110000000 = UNKNOWN_TORQUE + UNKNOWN_VOLTAGE_COMMAND

I believe the controller overspeed was the first fault. The motor errors appeared afterward because the controller stopped producing a valid torque command. The watchdog error appeared later as part of my independent safety cutoff.

An independent CAN logger sampled the encoder every approximately 50ms:

  • First count: -4920
  • Last count: -4811
  • Net movement: 109 counts
  • Movement: 0.00665 turns, approximately 2.4°
  • Maximum count-derived interval velocity: approximately 0.0244 turns/s
  • Peak Iq setpoint: 0.871A
  • Peak measured Iq: 0.462A
  • Bus voltage remained between 38.38V and 38.45V
  • No encoder SPI or CAN errors

Therefore, the slower 20Hz logger never measured more than 0.0244 turns/s, but the ODrive’s internal higher-rate velocity estimate apparently exceeded the 0.036 turns/s trip threshold between samples.

After a complete power cycle, both axes returned to IDLE with all errors zero. Calibration and saved configuration remained correct.

My questions are:

  1. Is this likely a real short acceleration caused by stick-slip/breakaway, or a spike in the encoder velocity estimator?
  2. Are velocity gains of 15/15 too aggressive for this low-speed hoverboard motor application?
  3. Is an encoder bandwidth of 100 rad/s appropriate for an AS5047P SPI encoder at approximately 0.02 turns/s?
  4. Is a 0.036 turns/s overspeed threshold simply too close to the commanded speed for initial commissioning?
  5. What conservative velocity gains, encoder bandwidth, and velocity-limit margin would you recommend for the next test?
  6. Is there anything specific to SPI absolute encoders and firmware 0.5.6 that I should check?

I have the complete 50ms CAN trace available if it would help.

Thank you.

Hi! Agreed the overspeed error is the root cause here.

Worth knowing that the overspeed check is instantaneous and runs in the control loop at 8 kHz — a single sample above vel_limit * vel_limit_tolerance latches the error. Your CAN log is 400x slower than that, so it will basically never contain the sample that tripped it. Not seeing it in the log doesn’t tell you much either way.

Is this likely a real short acceleration caused by stick-slip/breakaway, or a spike in the encoder velocity estimator?

Likely just acceleration. A tolerance of 1.2 is pretty small, especially given the noise in the AS5047P encoder (which are pretty noisy). I’d bump the tolerance much higher. I also think that 0.02 rev/s is just too small to do anything useful with. To elaborate, the AS5047P is so noisy that I don’t think you’ll be able to get very useful command at that speeds.

Are velocity gains of 15/15 too aggressive for this low-speed hoverboard motor application?

It really depends on your system. If the gains are stable and you’ve tuned them, then it’s fine.

Is an encoder bandwidth of 100 rad/s appropriate for an AS5047P SPI encoder at approximately 0.02 turns/s?

I’d keep it at the default of 1000.

Is a 0.036 turns/s overspeed threshold simply too close to the commanded speed for initial commissioning?

Yes, absolutely. I also think this encoder may not work for your requirements. The AS5047P is going to have about 0.068° RMS noise – that’s going to turn into a lot of velocity estimate noise.

What conservative velocity gains, encoder bandwidth, and velocity-limit margin would you recommend for the next test?

Keep the encoder bandwidth at 1000. Follow the tuning procedure for velocity gains, if you haven’t already. Bump the velocity limit margin to something really high, so that the trip threshold is at least a handful of rev/s.

That being said, I do think your gains may be too aggressive. 0.45Nm torque limit w/ a vel_gain of 15 will saturate at 0.03 rev/s of error, so you’re essentially running a bang-bang control loop against the encoder noise :slight_smile:

My question: why is your velocity limit so low? What do you actually need here? 0.02 rev/s == 1.2 RPM; does this application actually need to operate at such low speeds, or is this just for initial bringup / testing? If the former, you’ll definitely need a different encoder (much higher resolution & lower noise), if the latter, then is it an issue to just bump up the velocity limit?

Hi,

Thank you. This is very helpful and explains why the instantaneous sample causing the overspeed fault would not necessarily appear in our CAN log.

To answer your question: 0.02 turns/s was only an intentionally very slow initial commissioning / bring-up speed. It is not the intended operating speed of the application.

The expected operating range is approximately 0.1–1.0 turns/s, with the initial recommissioning tests likely progressing from around 0.1 to 0.25 and then 0.5 turns/s.

Before changing the configuration, I would appreciate your advice on a few points:

  1. Given an intended operating range of approximately 0.1–1.0 turns/s, rather than 0.02 turns/s, do you still consider the AS5047P unsuitable, or would you expect it to be adequate in this range?

  2. We understand your recommendation to return the encoder bandwidth to the default 1000 rad/s.

  3. For commissioning at approximately 0.1–0.5 turns/s, would you recommend setting the effective overspeed trip point around 3–5 turns/s, or would you suggest another range?

  4. Regarding vel_gain, we understand your point that our previous value of 15 was much too aggressive relative to the torque limit and very low test speed. We will follow the ODrive tuning procedure. Is there a conservative starting vel_gain you would recommend before beginning the tuning procedure?

  5. The AS5047P datasheet discusses Dynamic Angle Error Compensation and its effect on low-speed noise. Would you recommend changing this setting for operation in approximately the 0.1–1.0 turns/s range, or leaving the encoder configuration unchanged?

Thank you again for your help.

To answer your question: 0.02 turns/s was only an intentionally very slow initial commissioning / bring-up speed. It is not the intended operating speed of the application.
The expected operating range is approximately 0.1–1.0 turns/s, with the initial recommissioning tests likely progressing from around 0.1 to 0.25 and then 0.5 turns/s.

Fully understood!

Given an intended operating range of approximately 0.1–1.0 turns/s, rather than 0.02 turns/s, do you still consider the AS5047P unsuitable, or would you expect it to be adequate in this range?

I don’t think you’ll get the best performance, but I think it’ll work as an initial test.

For commissioning at approximately 0.1–0.5 turns/s, would you recommend setting the effective overspeed trip point around 3–5 turns/s, or would you suggest another range?

Overspeed is really just for a worst-case error. I think 3-5 is an okay range.

Regarding vel_gain, we understand your point that our previous value of 15 was much too aggressive relative to the torque limit and very low test speed. We will follow the ODrive tuning procedure. Is there a conservative starting vel_gain you would recommend before beginning the tuning procedure?

Usually I recommend starting very low, at 0.1 or below, then increasing per the tuning procedure.

The AS5047P datasheet discusses Dynamic Angle Error Compensation and its effect on low-speed noise. Would you recommend changing this setting for operation in approximately the 0.1–1.0 turns/s range, or leaving the encoder configuration unchanged?

Disabling DAEC will only reduce noise from 0.068° RMS to 0.052° RMS – it’s likely not worth the time/effort.

As an overall question, are you able to share a bit more as to your specific application and requirements? That way I can help recommend the overall solution.

Thank you, this is very helpful.

For some additional context without going too deeply into the mechanical design, the application is a low-speed, high-torque closed-loop BLDC positioning system with relatively high inertia and changing external load.

The current work is still in the commissioning phase. Our immediate goals are:

  • stable low-speed position and velocity control;
  • smooth acceleration/deceleration;
  • reliable position feedback;
  • avoiding false overspeed trips caused by estimator noise;
  • eventually increasing the operating speed beyond the initial commissioning range.

The approximately 0.1–1.0 turns/s range we mentioned is currently a provisional operating range rather than a finalized specification.

Based on your feedback, our next commissioning plan would therefore be approximately:

  • encoder bandwidth: 1000;
  • initial vel_gain: approximately 0.1 or below, then tune upward;
  • initial test velocity: approximately 0.1 turns/s;
  • progressive testing toward 0.25 and 0.5 turns/s;
  • effective overspeed trip point in approximately the 3–5 turns/s range;
  • leave AS5047P DAEC configuration unchanged for now.

Does that sound like a sensible starting point?

One additional question: for this type of low-speed, high-torque positioning application, if the AS5047P ultimately becomes the limiting factor, what encoder type or resolution would you normally recommend as the next step?

Thank you again for your help.