PLANETARYDRIVER

← Guides

ODrive SimpleFOC tutorial QDD

Build & Tune Your First FOC Actuator

PlanetaryDriver 1d ago12 views

Build & Tune Your First FOC Actuator

A practical, no-fluff walkthrough: take the QDD Actuator Starter Kit from a box of parts to a
smooth, closed-loop, backdrivable robot joint in about an hour. Written for people who've used a
soldering iron but maybe never tuned field-oriented control before.

This guide targets the kit's ODrive-compatible driver (stock ODrive 3.6 firmware /
odrivetool). For the Arduino-based Mini FOC Kit, jump to the SimpleFOC section.
Official references: ODrive docs · SimpleFOC alignment procedure.


What you'll need

  • The QDD Actuator Starter Kit (motor + ODrive-compatible driver + AS5047P encoder + magnet/mount)
  • A 24 V bench power supply (start at a 3–5 A limit) and a brake resistor (often included on the board)
  • A USB cable and a computer (Windows/Mac/Linux) with Python + pip install odrive
  • Basic tools, and thread-locker for the magnet/mount

⚠️ Safety: an actuator can spin or kick hard during calibration. Clamp the motor down, keep
fingers and cables clear, start with a low current/voltage limit, and keep one hand on the power
switch.


Step 1 — Mechanical assembly

  1. Mount the diametric magnet centered on the motor's rear shaft/rotor, axis-aligned. Concentricity
    matters — an off-center magnet = noisy angle readings. A drop of thread-locker keeps it put.
  2. Mount the encoder board facing the magnet at the gap in its datasheet (typically ~0.5–2 mm).
    Keep it square to the shaft.
  3. Fix the motor to a solid bracket. If you're adding a 6:1–10:1 planetary/cycloidal gearbox
    to make a true QDD actuator, mount it now but calibrate on the bare motor first.

Step 2 — Wiring

  1. Motor phases (A/B/C) → the three driver phase outputs. Order doesn't matter electrically —
    calibration sorts out direction.
  2. Encoder → driver SPI: connect MISO/MOSI/SCLK/CS + 3.3 V + GND per the kit's pinout.
  3. Power: 24 V supply → driver DC input (mind polarity!). Connect the brake resistor — FOC
    dumps energy back on deceleration and needs somewhere to put it.
  4. Double-check grounds before powering on.

Step 3 — Configure ODrive (the important part)

Connect USB, run odrivetool, then set the motor up. This motor is a gimbal motor (high
phase resistance, low current) — the single most common first-timer mistake is leaving it in
high-current mode. Use gimbal mode:

# --- Motor (gimbal-type: high resistance, voltage-controlled) ---
odrv0.axis0.motor.config.motor_type = MOTOR_TYPE_GIMBAL
odrv0.axis0.motor.config.pole_pairs = 11          # GM5208-class: count magnets ÷ 2
# In gimbal mode, current_lim / calibration_current are interpreted as VOLTS:
odrv0.axis0.motor.config.calibration_current = 5  # volts
odrv0.axis0.motor.config.current_lim = 8          # volts
odrv0.axis0.motor.config.torque_constant = 8.27 / KV   # use the motor's Kv

# --- Encoder (AS5047P, 14-bit absolute over SPI) ---
odrv0.axis0.encoder.config.mode = ENCODER_MODE_SPI_ABS_AMS
odrv0.axis0.encoder.config.cpr = 16384            # 2^14
odrv0.axis0.encoder.config.abs_spi_cs_gpio_pin = 5  # per your board

# --- Bus / brake ---
odrv0.config.dc_bus_overvoltage_trip_level = 28
odrv0.config.brake_resistance = 2.0               # ohms, per your resistor (0 if none)
odrv0.save_configuration()

Now run the one-time calibration — it measures the motor and learns the encoder offset and
direction:

odrv0.axis0.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE
# motor beeps/clicks, then the shaft sweeps to find the encoder offset
dump_errors(odrv0)        # should be clean

If it's clean, make the calibration stick so you don't repeat it every boot:

odrv0.axis0.encoder.config.pre_calibrated = True
odrv0.axis0.motor.config.pre_calibrated = True
odrv0.axis0.config.startup_closed_loop_control = True
odrv0.save_configuration()

Step 4 — Closed-loop control + tuning

odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
odrv0.axis0.controller.config.control_mode = CONTROL_MODE_POSITION_CONTROL
odrv0.axis0.controller.input_pos = 0

Tune the gains — start low and work up:

  • vel_gain — raise until the joint feels stiff; back off ~25% when it starts to buzz/whine.
  • pos_gain — raise for snappier position holding; too high → oscillation.
  • vel_integrator_gain — a rule of thumb is 0.5 * bandwidth * vel_gain; kills steady-state droop.

Test backdrivability: in torque mode with a low setpoint, the joint should move freely when you
push it — that's the whole point of QDD. Watch motor temperature; if it climbs at idle, your
voltage limit is too high or the encoder offset is off.

odrv0.save_configuration() when you're happy.


Common pitfalls (and the fixes)

Symptom Likely cause Fix
Vibrates, won't spin smoothly Wrong pole pairs Recount magnets ÷ 2; re-run calibration
Motor gets hot at standstill Left in high-current mode, or bad encoder offset Use MOTOR_TYPE_GIMBAL; recalibrate
Encoder error / noisy angle Magnet off-center or gap wrong Re-center magnet; set gap per datasheet
Overvoltage error on stop No/!wrong brake resistor Add resistor; set brake_resistance
Spins the wrong way Phase/encoder direction Calibration handles it — just re-run it
Calibration fails immediately Wiring/power Check phase + encoder wiring, PSU current limit

Appendix — the SimpleFOC (Arduino) path

If you're using the Mini FOC Kit (SimpleFOC-friendly driver + Arduino), the flow is the same
idea, different tools (alignment docs):

#include <SimpleFOC.h>

BLDCMotor motor = BLDCMotor(11);                 // pole pairs
BLDCDriver3PWM driver = BLDCDriver3PWM(/*pwm pins*/);
MagneticSensorSPI sensor = MagneticSensorSPI(AS5047_SPI, /*CS pin*/);

void setup() {
  sensor.init(); motor.linkSensor(&sensor);
  driver.voltage_power_supply = 24; driver.init(); motor.linkDriver(&driver);
  motor.voltage_limit = 6;                       // gimbal motor: keep it low
  motor.controller = MotionControlType::angle;
  motor.init();
  motor.initFOC();                               // runs the alignment procedure
}
void loop() { motor.loopFOC(); motor.move(target); }

initFOC() does the same three things ODrive's calibration does: pole-pair check, zero
electric angle
(records the sensor offset), and current-sense alignment. Once you have the
printed zero_electric_offset and sensor_direction, hard-code them to skip alignment on boot.
Same tuning logic: raise the velocity/angle PID gains until stiff, back off before it buzzes.


Stuck? That's what we're here for — reach out and we'll help you tune it. — [BRAND]

Comments (0)

Sign in to join the conversation.

No comments yet — be the first.