ODrive Documentation

High performance motor control

View the Project on GitHub madcowswe/ODrive

Help improve these docs: submit edits using the link in the top right.

If you need help, please search or ask the ODrive Community.

ODrive Firmware Developer Guide

This guide is intended for developers who wish to modify the firmware of the ODrive. As such it assumes that you know things like how to use Git, what a compiler is, etc. If that sounds scary, turn around now.

The official releases are maintained on the master branch. However since you are a developer, you are encouraged to use the devel branch, as it contains the latest features.

The project is under active development, so make sure to check the Changelog to keep track of updates.

Table of contents


The recommended tools for ODrive development are:

See below for specific installation instructions for your OS.

Depending on what you’re gonna do, you may not need all of the components.

Once you have everything, you can verify the correct installation by running:

$ arm-none-eabi-gcc --version
$ arm-none-eabi-gdb --version
$ openocd --version             # should be 0.10.0 or later
$ tup --version                 # should be 0.7.5 or later
$ python --version              # should be 3.7 or later

Linux (Ubuntu)

sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa
sudo apt-get update
sudo apt-get install gcc-arm-embedded
sudo apt-get install openocd
sudo add-apt-repository ppa:jonathonf/tup && sudo apt-get update && sudo apt-get install tup

Arch Linux

sudo pacman -S arm-none-eabi-gcc arm-none-eabi-binutils
sudo pacman -S arm-none-eabi-gdb
sudo pacman -S tup


First install Homebrew. Then you can run these commands in Terminal:

brew install armmbed/formulae/arm-none-eabi-gcc
brew cask install osxfuse && brew install tup
brew install openocd


Note: make sure these programs are not only installed but also added to your PATH.

Some instructions in this document may assume that you’re using a bash command prompt, such as the Windows 10 built-in bash or Git bash.

Configuring the build

To customize the compile time parameters, copy or rename the file Firmware/tup.config.default to Firmware/tup.config and edit the parameters in that file:

CONFIG_BOARD_VERSION: The board version you’re using. Can be v3.1, v3.2, v3.3, v3.4-24V, v3.4-48V, v3.5-24V, v3.5-48V, etc. Check for a label on the upper side of the ODrive to find out which version you have. Some ODrive versions don’t specify the voltage: in that case you can read the value of the main capacitors: 120uF are 48V ODrives, 470uF are 24V ODrives.

CONFIG_USB_PROTOCOL: Defines which protocol the ODrive should use on the USB interface.

Note: There is a second USB interface that is always a serial port.

CONFIG_UART_PROTOCOL: Defines which protocol the ODrive should use on the UART interface (GPIO1 and GPIO2). Note that UART is only supported on ODrive v3.3 and higher.

You can also modify the compile-time defaults for all .config parameters. You will find them if you search for AxisConfig, MotorConfig, etc.

Building and flashing the Firmware

  1. Run make in the Firmware directory.
  2. Connect the ODrive via USB and power it up.
  3. Flash the firmware using odrivetool dfu.

Flashing using an STLink/v2 programmer

If the flashing worked, you can connect to the board using the odrivetool.


The script tools/run_tests.py runs a sequence of automated tests for several firmware features as well as high power burn-in tests. Some tests only need one ODrive and one motor/encoder pair while other tests need a back-to-back test rig such as this one. In any case, to run the tests you need to provide a YAML file that lists the parameters of your test setup. An example can be found at tools/test-rig-parallel.yaml. The programmer serial number can be found by running Firmware/find_programmer.sh (make sure it has the latest formware from STM).

The test script commands the ODrive to high currents and high motor speeds so if your ODrive is connected to anything other than a stirdy test-rig (or free spinning motors), it will probably break your machine.

Example usage: ./run_tests.py --test-rig-yaml ../tools/test-rig-parallel.yaml


If you’re using VSCode, make sure you have the Cortex Debug extension, OpenOCD, and the STLink. You can verify that OpenOCD and STLink are working by ensuring you can flash code. Open the ODrive_Workspace.code-workspace file, and start a debugging session (F5). VSCode will pick up the correct settings from the workspace and automatically connect. Breakpoints can be added graphically in VSCode.

Setting up an IDE

For working with the ODrive code you don’t need an IDE, but the open-source IDE VSCode is recommended. It is also possible to use Eclipse. If you’d like to go that route, please see the respective configuration document:


This project uses the STM32CubeMX tool to generate startup code and to ease the configuration of the peripherals. You can download it from here. All CubeMX related files are in Firmware/Board/v3.

You will likely want the pinout for this process. It is available here.

Maintaining modified generated code

When generating the code, STM32CubeMX will nuke everything except some special sections that they provide. These sections are marked like USER CODE BEGINUSER CODE END. We used to try to make sure all edits we made to the generated code would only go in these sections, so some code structrure may reflect that. However over time we realized this will not be tenable, so instead we use git to rebase all changes of the generated code whenever we need to regenerate it. We use two special branches that will help us to do this, they are STM32CubeMX-start and STM32CubeMX-end. How to use these is shown in the following example.

Note: Due to how this rebasing is done, all development that changes the generated code should be done directly on STM32CubeMX-end, and not based on devel, then follow step 4 below to carry them over to your feature branch. If you did some changes to the generated code based from devel, you need to cherry pick just those changes over to STM32CubeMX-end.

1. Ensuring a clean slate

2. Making changes to the STM32CubeMX config

3. Rebasing the modifications to the generated code

4. Merge new STM32CubeMX code to your feature branch

Simply merge the new state at STM32CubeMX-end into your feature branch.

5. Pushing back upstream


LIBUSB_ERROR_IO when flashing with the STLink/v2

Problem: when I try to flash the ODrive with the STLink using make flash I get this error:

Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 2000 kHz
adapter_nsrst_delay: 100
none separate
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : clock speed 1800 kHz
Error: libusb_open() failed with LIBUSB_ERROR_IO
Error: open failed
in procedure 'init' 
in procedure 'ocd_bouncer'

Solution: This happens from time to time.

  1. Unplug the STLink and all ODrives from your computer
  2. Power off the ODrive you’re trying to flash
  3. Plug in the STLink into your computer
  4. Power on the ODrive
  5. Run make flash again


All *.md files in the docs/ directory of the master branch are served up by GitHub Pages on this domain.

To run the docs server locally:

cd docs
gem install bundler
bundle install --path ruby-bundle
bundle exec jekyll serve --host=


We use GitHub Releases to provide firmware releases.

  1. Cut off the changelog to reflect the new release
  2. Merge the release candidate into master.
  3. Push a (lightweight) tag to the master branch. Follow the existing naming convention.
  4. Push the python tools to PyPI.
  5. Edit the release on GitHub to add a title and description (copy&paste from changelog).

Other code maintenance notes

The cortex M4F processor has hardware single precision float unit. However double precision operations are not accelerated, and hence should be avoided. The following regex is helpful for cleaning out double constants: find: ([-+]?[0-9]+\.[0-9]+(?:[eE][-+]?[0-9]+)?)([^f0-9e]) replace: \1f\2

Notes for Contributors

In general the project uses the Google C++ Style Guide, except that the default indendtation is 4 spaces, and that the 80 character limit is not very strictly enforced, merely encouraged.