diff --git a/Firmware/README.md b/Firmware/README.md deleted file mode 100644 index 0346a9bf..00000000 --- a/Firmware/README.md +++ /dev/null @@ -1,340 +0,0 @@ -# ODriveFirmware - -If you wish to use the latest release, please use the `master` branch (this is the default branch GitHub will present you with). - -If 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](CHANGELOG.md) to keep track of updates. - -### Table of contents - - - -- [Configuring the build](#configuring-the-build) -- [Downloading and Installing Tools](#downloading-and-installing-tools) -- [Building and Flashing the Firmware](#building-and-flashing-the-firmware) -- [Setting up an IDE](#setting-up-an-ide) -- [Communicating over USB or UART](#communicating-over-usb-or-uart) -- [Configuring parameters](#configuring-parameters) -- [Encoder Calibration](#encoder-calibration) -- [Checking for error codes](#checking-for-error-codes) -- [Generating startup code](#generating-startup-code) -- [Notes for Contributors](#notes-for-contributors) - - - -

-## Configuring the build -To correctly operate the ODrive, you need to supply some parameters. Some are mandatory, and if supplied incorrectly will cause the drive to malfunction. -In this section we will set the compile-time parameters, later we will also set the [run time parameters](#configuring-parameters). - -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` or `v3.4-48V`. Check for a label on the upper side of the ODrive to find out which version you have. - -__CONFIG_USB_PROTOCOL__: Defines which protocol the ODrive should use on the USB interface. - * `native`: The native ODrive protocol. Use this if you want to use the python tools in this repo. - * `native-stream`: Like the native ODrive protocol, but the ODrive will treat the USB connection exactly as if it was a UART connection. __Use this if you're on macOS__. This is necessary because macOS doesn't grant our python tools sufficient low-level access to treat the device as the USB device that it is. - * `ascii`: The ASCII protocol. This allows sending simple commands like position setpoints directly from the terminal to the ODrive without the use of intermediate utilities. - * `none`: Disable USB. The device will still show up when plugged in but it will ignore any commands. - -__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. - * `native`: The native ODrive protocol. Use this if you're connecting the ODrive to a PC using UART and want to use the python tools to control and setup the ODrive. - * `ascii`: The ASCII protocol. Use this option if you control the ODrive with an Arduino. The ODrive Arduino library is not yet updated to the native protocol. - * `none`: Disable UART. - - -

-## Downloading and Installing Tools -### Getting a programmer -__Note:__ If you don't plan to make major firmware modifications you can use the built-in DFU feature. -In this case you don't need an SWD programmer and you can skip OpenOCD related instructions. - -Get a programmer that supports SWD (Serial Wire Debugging) and is ST-link v2 compatible. You can get them really cheap on [eBay](http://www.ebay.co.uk/itm/ST-Link-V2-Emulator-Downloader-Programming-Mini-Unit-STM8-STM32-with-20CM-Line-/391173940927?hash=item5b13c8a6bf:g:3g8AAOSw~OdVf-Tu) or many other places. - -### Installing prerequisites -To compile the program, you first need to install the prerequisite tools: - -#### Linux: -* `gcc-arm-none-eabi`: GCC compilation toolchain for ARM microcontrollers. - * Installing on Ubuntu: `sudo apt-get install gcc-arm-none-eabi` - * Installing on Arch Linux: `sudo pacman -S arm-none-eabi-gcc arm-none-eabi-binutils` -* `gdb-arm-none-eabi`: GNU project debugger for ARM microcontrollers. - * Installing on Ubuntu: `sudo apt-get install gdb-arm-none-eabi` - * Installing on Arch Linux: `sudo pacman -S arm-none-eabi-gdb` -* `OpenOCD`: Open On-Chip Debugging tools. This is what we use to flash the code onto the microcontroller. - * Installing on Ubuntu: `sudo apt-get install openocd` - * Installing on Arch Linux: build and install the [AUR package](https://aur.archlinux.org/packages/openocd/) -* `tup`: Used as a build tool - * Installing on Ubuntu: `sudo add-apt-repository ppa:jonathonf/tup; sudo apt-get update; sudo apt-get install tup` - * Installing on Arch Linux: `sudo pacman -S tup` -* No additional USB CDC driver should be required on Linux. - -#### Mac: -* `brew cask install gcc-arm-embedded`: GCC toolchain+debugger -* `brew cask install osxfuse; brew install tup`: Build tool -* `brew install openocd`: Programmer - -#### Windows: -Install the following: -* [Git for windows](https://git-scm.com/download/win). This intalls the Git Bash, which is a unix style command line interface that we will be using. -* [GNU ARM Embedded Toolchain](https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads). The cross-compiler used to compile the code. Download and install the "Windows 32-bit" version. Make sure to tick the "add to path" option. -* [Tup](http://gittup.org/tup/index.html) is used to script the compilation process. Unpack the zip-folder. Then add the path that contains the executable (something like `C:\Users\yourname\the\place\you\unzipped\tup-latest`) to your PATH environment variable. For details on how to set your path envirment in windows see [these instructions.](https://www.java.com/en/download/help/path.xml) -* [Make for Windows](http://gnuwin32.sourceforge.net/packages/make.htm). This is optional and mainly used because commands are short and developers are used to it. If you don't want to use it, you can look at the Makefile to get the corresponding commands. Download and run the complete package setup program. Add the path of the binaries to your PATH environment variable. For me this was at `C:\Program Files (x86)\GnuWin32\bin`. -* OpenOCD. Follow the instructions at [GNU ARM Eclipse - How to install the OpenOCD binaries](http://gnuarmeclipse.github.io/openocd/install/), including the part about ST-LINK/V2 drivers. Add the path of the binaries to your PATH environment variable. For me this was at `C:\Program Files\GNU ARM Eclipse\OpenOCD\0.10.0-201704182147-dev\bin`. - -After installing all of the above, open a Git Bash shell. Continue at section [Building and Flashing the Firmware](#building-and-flashing-the-firmware). - -

-## Building and Flashing the Firmware - -### Building the firmware -* Make sure you have cloned the repository. It is recommended that you use [git](https://help.github.com/articles/cloning-a-repository/) for this. -* Navigate your terminal (windows: Git Bash/cygwin) to the ODrive/Firmware dir. -* Run `make` in the `Firmware` directory. - -### Flashing the firmware (standalone device) -Note: ODrive v3.4 and earlier require you to flash with the external programmer first (see below), before you can reflash in standalone mode. -* __Windows__: Use the [Zadig](http://zadig.akeo.ie/) utility to set ODrive (not STLink!) driver to libusb-win32. - * If 'Odrive version 3.x' is not in the list of devices upon opening Zadig, check 'List All Devices' from the options menu. With the Odrive selected in the device list choose 'libusb-win32' from the target driver list and select the large 'install driver' button. -* Run `make dfu` in the `Firmware` directory. -* __Windows__: During the update, a new device called "STM32 BOOTLOADER" will appear. Open the Zadig utility that you used when you first connected your ODrive and set the driver for "STM32 BOOTLOADER" to libusb-win32. After that the firmware upgrade will continue. -* 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 you have multiple ODrives connected, you should specify which one to upgrade. -* Run `(lsusb -d 1209:0d32 -v; lsusb -d 0483:df11 -v) 2>/dev/null | grep iSerial` to list the serial number of all flashable devices. Example output: -``` - iSerial 3 385F324D3037 - iSerial 3 306A396A3235 -``` -* The last column is the serial number you're looking for. You can unplug selected devices to track down the one you want to update. -* Run `make dfu SERIAL_NUMBER=385F324D3037`, where `385F324D3037` is the targeted serial number. - -__Warning:__ Currently it is advised that you only do this to flash -official unmodified firmware. Also make sure you don't switch off -the device during upgrade. Otherwise, if something goes wrong, you need an external -programmer to recover the device. This will be fixed in the future. - -### Flashing the firmware (external Programmer) -* **Make sure you have [configured the parameters first](#configuring-parameters)** -* Connect `GND`, `SWD`, and `SWC` on connector J2 to the programmer. Note: Always plug in `GND` first! -* You need to power the board by only **ONE** of the following: VCC(3.3v), 5V, or the main power connection (the DC bus). The USB port (J1) does not power the board. -* Run `make flash` in the `Firmware` directory. - -If the flashing worked, you can start sending commands. If you want to do that now, you can go to [Communicating over USB or UART](#communicating-over-usb-or-uart). - -### Debugging the firmware -* Run `make gdb`. This will reset and halt at program start. Now you can set breakpoints and run the program. If you know how to use gdb, you are good to go. - -

-## 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: - -* [Configuring VSCode](configuring-vscode.md) -* [Configuring Eclipse](configuring-eclipse.md) - -

-## Communicating over USB or UART -Warning: If testing USB or UART communication for the first time it is recommend that your motors are free to spin continuously and are not connected to a drivetrain with limited travel. -### From Linux/Windows/macOS -There are two example python scripts to help you get started with controlling the ODrive using python. One will drop you into an interactive shell to query settings, parameters, and variables, and let you send setpoints manually ([tools/explore_odrive.py](../tools/explore_odrive.py)). The other is a demo application to show you how to control the ODrive programmatically ([tools/demo.py](../tools/demo.py)). Below follows a step-by-step guide on how to run these. - - -* __Windows__: It is recommended to use a Unix style command prompt, such as Git Bash that comes with [Git for windows](https://git-scm.com/download/win). - -1. [Install Python 3](https://www.python.org/downloads/), then install dependencies pyusb and pyserial: -``` -pip install pyusb pyserial -``` -* Note: If you have python2 and python3 installed concurrently then you must specifiy that we wish to target python3. This is done as follows: - * __Linux__: Use `pip3` instead of `pip` in the above command. - * __Windows__: Use the full path of the Python3 pip, yeilding something like: - `C:\Users\YOUR_USERNAME\AppData\Local\Programs\Python\Python36-32\Scripts\pip install pyusb pyserial` -* If you have trouble with this step then refer to [this walkthrough.](https://www.youtube.com/watch?v=jnpC_Ib_lbc) - -2. __Linux__: set up USB permissions -``` - echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="0d[0-9][0-9]", MODE="0666"' | sudo tee /etc/udev/rules.d/50-odrive.rules - sudo udevadm control --reload-rules - sudo udevadm trigger # until you reboot you may need to do this everytime you reset the ODrive -``` -3. Power the ODrive board (as per the [Flashing the firmware](#flashing-the-firmware) step). -4. Plug in a USB cable into the microUSB connector on ODrive, and connect it to your PC. -5. __Windows__: Use the [Zadig](http://zadig.akeo.ie/) utility to set ODrive (not STLink!) driver to libusb-win32. - * If 'Odrive version 3.x' is not in the list of devices upon opening Zadig, check 'List All Devices' from the options menu. With the Odrive selected in the device list choose 'libusb-win32' from the target driver list and select the large 'install driver' button. -6. Open the bash prompt in the `ODrive/tools/` folder. -7. Run `python3 demo.py` or `python3 explore_odrive.py`. -- `demo.py` is a very simple script which will make motor 0 turn back and forth. Use this as an example if you want to control the ODrive yourself programatically. -- `explore_odrive.py` drops you into an interactive python shell where you can explore and edit the parameters that are available on your device. For instance `my_odrive.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/explore_odrive.py --discover serial`. - -### From Arduino -[See ODrive Arduino Library](https://github.com/madcowswe/ODriveArduino) - -### Other platforms -See the [protocol specification](protocol.md) or the [ASCII protocol specification](ascii-protocol.md). - -

-## Configuring parameters -The majority of the important parameters you would want to set after flashing the ODrive with firmware are configurable over the USB communication interface. These include some mandatory parameters that you must set for correct operation, as well as tuning and optional parameters. -To start the configuration session: - -* Launch `./tools/explore_odrive.py`. This will give you a command prompt where you can modify using simple assignments. -* Configure parameters of the `my_odrive.[...].config` objects. - * For example to adjust the position gain: `my_odrive.motor0.config.pos_gain = 30` Enter. - * The complete list of configurable parameters is: - * `my_odrive.motorN.config.*` - * `my_odrive.axisN.config.*` - * where N is a valid motor number (0 or 1). -* Save the configuration into non-volatile memory: `my_odrive.save_configuration()` Enter - * This will save the properties of all the `[...].config` objects and no other parameters. -* Reboot the drive: `my_odrive.reboot()` Enter - -Note that a firmware upgrade at this point will preserve the configuration if and only if the parameters of both firmware versions are identical. Should you need to reset the configuration, you can run `my_odrive.erase_configuration()`. - -__Developers__: Be aware that you can also modify the compile-time defaults for all of these parameters. Most of them you will find at the top of [MotorControl/low_level.c](MotorControl/low_level.c#L50). Note that the configuration parameters there are somewhat intertwined with runtime variables and hardware specific configuration that should not be changed. Also note that all parameters occur twice. - -### Mandatory parameters -You must set for every motor: -* `my_odrive.motorN.encoder.config.cpr`: Encoder Count Per Revolution (CPR). This is 4x the Pulse Per Revolution (PPR) value. -* `my_odrive.motorN.config.pole_pairs`: This is the number of magnet poles in the rotor, **divided by two**. You can simply count the number of permanent magnets in the rotor, if you can see them. Note: this is not the same as the number of coils in the stator. -* `my_odrive.config.brake_resistance` [Ohm]: This is the resistance of the brake resistor. If you are not using it, you may set it to 0.0f. -* `my_odrive.motorN.config.motor_type`: This is the type of motor being used. Currently two types of motors are supported -- High-current motors (`MOTOR_TYPE_HIGH_CURRENT`) and Gimbal motors (`MOTOR_TYPE_GIMBAL`). - -#### Motor Modes -If you're using a regular hobby brushless motor like [this](https://hobbyking.com/en_us/turnigy-aerodrive-sk3-5065-236kv-brushless-outrunner-motor.html) one, you should set `motor_mode` to `MOTOR_TYPE_HIGH_CURRENT`. For low-current gimbal motors like [this](https://hobbyking.com/en_us/turnigy-hd-5208-brushless-gimbal-motor-bldc.html) one, you should choose `MOTOR_TYPE_GIMBAL`. Do not use `MOTOR_TYPE_GIMBAL` on a motor that is not a gimbal motor, as it may overheat the motor or the ODrive. - -**Further detail:** -If 100's of mA of current noise is "small" for you, you can choose `MOTOR_TYPE_HIGH_CURRENT`. -If 100's of mA of current noise is "large" for you, and you do not intend to spin the motor very fast (omega * L << R), and the motor is fairly large resistance (1 ohm or larger), you can chose `MOTOR_TYPE_GIMBAL`. -If 100's of mA current noise is "large" for you, and you intend to spin the motor fast, then you need to replace the shunt resistors on the ODrive. - -### Tuning parameters -The most important parameters are the limits: -* The current limit: `my_odrive.motorN.current_control.config.current_lim` [A]. The default current limit, for safety reasons, is set to 10A. This is quite weak, and good for making sure the drive is stable. Once you have tuned the drive, you can increase this to 75A to get some performance. Note that above 75A, you must change the current amplifier gains. - * Note: The motor current and the current drawn from the power supply is not the same in general. You should not look at the power supply current to see what is going on with the motor current. -* The velocity limit: `my_odrive.motorN.config.vel_limit` [counts/s]. The motor will be limited to this speed; again the default value is quite slow. -* You can change `my_odrive.motorN.config.calibration_current` [A] to the largest value you feel comfortable leaving running through the motor continously when the motor is stationary. - -The motion control gains are currently manually tuned: -* `my_odrive.motorN.config.pos_gain = 20.0f` [(counts/s) / counts] -* `my_odrive.motorN.config.vel_gain = 15.0f / 10000.0f` [A/(counts/s)] -* `my_odrive.motorN.config.vel_integrator_gain = 10.0f / 10000.0f` [A/(counts/s * s)] - -An upcoming feature will enable automatic tuning. Until then, here is a rough tuning procedure: -* Set the integrator gain to 0 -* Make sure you have a stable system. If it is not, decrease all gains until you have one. -* Increase `vel_gain` by around 30% per iteration until the motor exhibits some vibration. -* Back down `vel_gain` to 50% of the vibrating value. -* Increase `pos_gain` by around 30% per iteration until you see some overshoot. -* Back down `pos_gain` until you do not have overshoot anymore. -* The integrator is not easily tuned, nor is it strictly required. Tune at your own discression. - -### Optional parameters -By default both motors are enabled, and the default control mode is position control. -If you want a different mode, you can change `my_odrive.motorN.config.control_mode`. -Possible values are: -* `CTRL_MODE_POSITION_CONTROL` -* `CTRL_MODE_VELOCITY_CONTROL` -* `CTRL_MODE_CURRENT_CONTROL` -* `CTRL_MODE_VOLTAGE_CONTROL` - this one is not normally used. - -To disable a motor at startup, set `my_odrive.axisN.config.enable_control` and `my_odrive.axisN.config.do_calibration` to `False`. - -

-## Encoder Calibration -By default the encoder-to-motor calibration will run on every startup. During encoder calibration the rotor must be allowed to rotate without any biased load during startup. That means mass and weak friction loads are fine, but gravity or spring loads are not okay. - -### Encoder with Index signal -If you have an encoder with an index (Z) signal, you may avoid having to do the calibration on every startup, and instead use the index signal to re-sync the encoder to a stored calibration. Below are the steps to do the one-time calibration and configuration. Note that you can follow these steps with one motor at a time, or all motors together, as you wish. - -* Since you will only do this once, it is recommended that you mechanically disengage the motor from anything other than the encoder, so it can spin freely. -* All the parameters we will be modifying are in the motor structs at the top of [MotorControl/low_level.c](MotorControl/low_level.c). -* Set `.encoder.use_index = true` and `.encoder.calibrated = false`. -* Flash this configuration, and let the motor scan for the index pulse and then complete the encoder calibration. -* Run `explore_odrive.py`, check [Communicating over USB or UART](#communicating-over-usb-or-uart) for instructions on how to do that. -* Enter the following to print out the calibration parameters (substitute the motor number you are calibrating for ``): - * `my_odrive.motor.encoder.encoder_offset` - This should print a number, like -326 or 1364. - * `my_odrive.motor.encoder.motor_dir` - This should print 1 or -1. -* Copy these numbers to the corresponding entries in low_level.c: `.encoder.encoder_offset` and `.encoder.motor_dir`. - * _Warning_: Please be careful to enter the correct numbers, and not to confuse the motor channels. Incorrect values may cause the motor to spin out of control. -* Set `.encoder.calibrated = true`. -* Flash this configuration and check that the motor scans for the index pulse but skips the encoder calibration. -* Congratulations, you are now done. You may now attach the motor to your mechanical load. -* If you wish to scan for the index pulse in the other direction (if for example your axis usually starts close to a hard-stop), you can set a negative value in `.encoder.idx_search_speed`. -* If your motor has problems reaching the index location due to the mechanical load, you can increase `.calibration_current`. - -

-## Checking for error codes -`explore_odrive.py`can also be used to check error codes when your odrive is not working as expected. For example `my_odrive.motor0.error` will list the error code associated with motor 0. -

-The error nummber corresponds to the following: - -0. `ERROR_NO_ERROR` -1. `ERROR_PHASE_RESISTANCE_TIMING` -2. `ERROR_PHASE_RESISTANCE_MEASUREMENT_TIMEOUT` -3. `ERROR_PHASE_RESISTANCE_OUT_OF_RANGE` -4. `ERROR_PHASE_INDUCTANCE_TIMING` -5. `ERROR_PHASE_INDUCTANCE_MEASUREMENT_TIMEOUT` -6. `ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE` -7. `ERROR_ENCODER_RESPONSE` -8. `ERROR_ENCODER_MEASUREMENT_TIMEOUT` -9. `ERROR_ADC_FAILED` -10. `ERROR_CALIBRATION_TIMING` -11. `ERROR_FOC_TIMING` -12. `ERROR_FOC_MEASUREMENT_TIMEOUT` -13. `ERROR_SCAN_MOTOR_TIMING` -14. `ERROR_FOC_VOLTAGE_TIMING` -15. `ERROR_GATEDRIVER_INVALID_GAIN` -16. `ERROR_PWM_SRC_FAIL` -17. `ERROR_UNEXPECTED_STEP_SRC` -18. `ERROR_POS_CTRL_DURING_SENSORLESS` -19. `ERROR_SPIN_UP_TIMEOUT` -20. `ERROR_DRV_FAULT` -21. `ERROR_NOT_IMPLEMENTED_MOTOR_TYPE` -22. `ERROR_ENCODER_CPR_OUT_OF_RANGE` -23. `ERROR_DC_BUS_BROWNOUT` - -If you get an error code larger than this, it may be the case that someone added a code and forgot to update the documentation. In that case, please check [MotorControl/low_level.h](MotorControl/low_level.h) for the full enum. - -

-## Generating startup code -**Note:** You do not need to run this step to program the board. This is only required if you wish to update the auto generated code. - -This project uses the STM32CubeMX tool to generate startup code and to ease the configuration of the peripherals. -You will likely want the pinout for this process. It is available [here](https://docs.google.com/spreadsheets/d/1QXDCs1IRtUyG__M_9WruWOheywb-GhOwFtfPcHuN2Fg/edit#gid=404444347) - -### Installing prerequisites -* `stm32cubeMX`: Tool from STM to automatically generate setup routines and configure libraries, etc. - * Available [here](http://www2.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-configurators-and-code-generators/stm32cubemx.html?icmp=stm32cubemx_pron_pr-stm32cubef2_apr2014&sc=stm32cube-pr2) - -### Generate code -* Run stm32cubeMX and load the `stm32cubemx/Odrive.ioc` project file. -* Press `Project -> Generate code` -* You may need to let it download some drivers and such. -* After generating/updating the code, some minor patches need to be applied. To do this, run: - `git apply Firmware/Board/v3/*.patch` -* Run `git config --local core.autocrlf input`. This will tell git that all files should be checked in with LF endings (CubeMX generates CRLF endings). -* `git status` will still claim that many files are modified but the actual diff (using `git diff`) is empty (apart from all the line ending warnings). - -### Generating patchfiles -If you made changes to CubeMX generated files outside of the `USER CODE BEGIN`...`USER CODE END` sections and contribute them back, please add a patch file so that the next person who runs CubeMX doesn't run into problems. - -CubeMX will reset everything outside these sections to the original state; we will capturing into a patch file the changes required to undo this resetting. -* Make sure your current desired state is committed. -* Make a new temporary branch: `git checkout -b cubemx_temp` -* Run the CubeMX code generation as described in the previous section, including applying previous patches. -* The diff will now _not_ be empty since CubeMX reset your changes. -* Stage this state and commit it with a message like "CubeMX reset my changes". -* Run `git revert HEAD` to undo the resetting action CubeMX's regeneration had. This is the commit which you will export, so write a meaningful commit message. -* Run `git format-patch HEAD~1` to export the commit as patch file. -* Check out your previous branch and then force-delete the temporary branch: `git branch -D cubemx_temp` -* Move the patch file to `Board/v3/` and add it in a new commit. - -

-## Notes for Contributors -In general the project uses the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html), except that the default indendtation is 4 spaces, and that the 80 character limit is not very strictly enforced, merely encouraged. - -### 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` diff --git a/ODrive_Workspace.code-workspace b/ODrive_Workspace.code-workspace index 60d370af..3afda0a7 100644 --- a/ODrive_Workspace.code-workspace +++ b/ODrive_Workspace.code-workspace @@ -5,6 +5,9 @@ }, { "path": "tools" + }, + { + "path": "docs" } ], "settings": { @@ -41,4 +44,4 @@ "algorithm": "cpp" } } -} \ No newline at end of file +} diff --git a/README.md b/README.md index 192a3c6b..3b4ece0b 100644 --- a/README.md +++ b/README.md @@ -7,68 +7,18 @@ This project is all about accurately driving brushless motors, for cheap. The ai | master | [![Build Status](https://travis-ci.org/madcowswe/ODrive.png?branch=master)](https://travis-ci.org/madcowswe/ODrive) | | devel | [![Build Status](https://travis-ci.org/madcowswe/ODrive.png?branch=devel)](https://travis-ci.org/madcowswe/ODrive) | -## Getting Started -It is perfectly fine, and even recommended, to start testing with just a single motor and encoder. -Make sure you have a good mechanical connection between the encoder and the motor, slip can cause disasterous oscillations. -All non-power I/O is 3.3V output and 5V tolerant on input, except: -* GPIO 3 and GPIO 4 are NOT 5V tolerant on ODrive v3.2 and earlier. -You need one or two [brushless motors](https://hackaday.io/project/11583-odrive-high-performance-motor-control/log/37666-hobby-motors-in-your-robots), [quadrature incremental encoder(s)](https://discourse.odriverobotics.com/t/which-encoders-to-choose/63/2), and a power resistor. +Please refer to the [Developer Guide](https://docs.odriverobotics.com/developer-guide.md) to get started with ODrive firmware development. -*The power resistor values you need depends on your motor setup, and peak/average decelleration power. A good starting point would be a [0.47 ohm, 50W resistor](https://www.digikey.com/product-detail/en/te-connectivity-passive-product/HSA50R47J/A102181-ND/2056131).* -Wire up the motor phases into the 3-phase screw terminals, and the power resistor to the AUX terminal. Wire up the power source (12-24V) to the DC terminal, make sure to pay attention to the polarity. Do not apply power just yet. +### Repository Structure + * **Firmware**: ODrive firmware + * **tools**: Python library & tools + * **docs**: Documentation -Wire up 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. +### Other Resources -![Image of ODrive all hooked up](https://docs.google.com/drawings/d/e/2PACX-1vTCD0P40Cd-wvD7Fl8UYEaxp3_UL81oI4qUVqrrCJPi6tkJeSs2rsffIXQRpdu6rNZs6-2mRKKYtILG/pub?w=1716&h=1281) - -The currently supported command modes are USB, UART and step/direction. -* If you are sending commands over USB, you can plug in a cable into the micro-USB port. -* If you are sending commands over UART, please see [Setting up UART](#setting-up-uart) -* If you are using step/direction, please see [setting up step/direction](#setting-up-stepdirection) - -You can now: -* [Download and build the firmware](Firmware/README.md) -* [Configure the firmware parameters](Firmware/README.md#configuring-parameters) -* [Flash the board](Firmware/README.md#flashing-the-firmware) - -### Startup procedure -The startup procedure is demonstrated [here](https://www.youtube.com/watch?v=VCX1bA2xnuY). - -Note: the rotor must be allowed to rotate without any biased load during startup. That means mass and weak friction loads are fine, but gravity or spring loads are not okay. Also note that in the video, the motors spin after initalisation, but in the current software the default behaviour is to do position control to position 0 (i.e. the position at startup) - -If you have an encoder with an index (Z) signal, you can calibrate once and restore the calibration on startup. Instructions on how to do that are [here](Firmware/README.md#encoder-calibration). - -### Sending commands -Sending USB and UART commands is documented [here](Firmware/README.md#communicating-over-usb-and-uart). You can also have a look at the [ODrive Arduino library](https://github.com/madcowswe/ODriveArduino) that makes it easy to use the UART interface on Arduino. You can also look at it as an implementation example of how to talk to the ODrive over UART. - -### Setting up UART -Baud rate: 115200 -Pinout: -* GPIO 1: Tx (connect to Rx of other device) -* GPIO 2: Rx (connect to Tx of other device) -* GND: you must connect the grounds of the devices together. Use any GND pin on J3 of the ODrive. - -To enable UART mode for the GPIO, please see [Setting the GPIO mode](Firmware/README.md#communication-configuration). - -### Setting up Step/Direction -Pinout: -* GPIO 1: M0 step -* GPIO 2: M0 dir -* GPIO 3: M1 step -* GPIO 4: M1 dir -* GND: you must connect the grounds of the devices together. Use any GND pin on J3 of the ODrive. - -Please note that GPIO_3 and GPIO_4 are NOT 5v tolerant on ODrive v3.2 and earlier, so 3.3V signals only! -ODrive v3.3 and onward have 5V tolerant GPIO pins. - -To enable step/dir mode for the GPIO, please see [Setting the GPIO mode](Firmware/README.md#communication-configuration). - -There is also a new config variable called `counts_per_step`, which specifies how many encoder counts a "step" corresponds to. It can be any floating point value. -The maximum step rate is pending tests, but it should handle at least 16kHz. If you want's to test it, please be aware that the failure mode on too high step rates is expected to be that the motors shuts down and coasts. - -Please be aware that there is no enable line right now, and the step/direction interface is enabled by default, and remains active as long as the ODrive is in position control mode. By default the ODrive starts in position control mode, so you don't need to send any commands over USB to get going. You can still send USB commands if you want to. - -### Getting help -If you have any issues or any questions please get in touch. The [ODrive Community](https://discourse.odriverobotics.com/) warmly welcomes you. + * [Main Website](https://www.odriverobotics.com/) + * [User Guide](https://docs.odriverobotics.com/quick-start.md) + * [Forum](https://discourse.odriverobotics.com/) + * [Chat](https://discourse.odriverobotics.com/t/come-chat-with-us/281) diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..66ad8894 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,3 @@ +/ruby-bundle +/_site +/.bundle \ No newline at end of file diff --git a/Firmware/CHANGELOG.md b/docs/CHANGELOG.md similarity index 100% rename from Firmware/CHANGELOG.md rename to docs/CHANGELOG.md diff --git a/docs/Gemfile b/docs/Gemfile new file mode 100644 index 00000000..37f5eaa4 --- /dev/null +++ b/docs/Gemfile @@ -0,0 +1,2 @@ +source 'https://rubygems.org' +gem 'github-pages', group: :jekyll_plugins diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock new file mode 100644 index 00000000..a8c1c4a1 --- /dev/null +++ b/docs/Gemfile.lock @@ -0,0 +1,250 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (4.2.9) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.11.1) + colorator (1.1.0) + commonmarker (0.17.9) + ruby-enum (~> 0.5) + concurrent-ruby (1.0.5) + em-websocket (0.5.1) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0.6.0) + ethon (0.11.0) + ffi (>= 1.3.0) + eventmachine (1.2.5) + execjs (2.7.0) + faraday (0.14.0) + multipart-post (>= 1.2, < 3) + ffi (1.9.23) + forwardable-extended (2.6.0) + gemoji (3.0.0) + github-pages (181) + activesupport (= 4.2.9) + github-pages-health-check (= 1.4.0) + jekyll (= 3.7.3) + jekyll-avatar (= 0.5.0) + jekyll-coffeescript (= 1.1.1) + jekyll-commonmark-ghpages (= 0.1.5) + jekyll-default-layout (= 0.1.4) + jekyll-feed (= 0.9.3) + jekyll-gist (= 1.5.0) + jekyll-github-metadata (= 2.9.4) + jekyll-mentions (= 1.3.0) + jekyll-optional-front-matter (= 0.3.0) + jekyll-paginate (= 1.1.0) + jekyll-readme-index (= 0.2.0) + jekyll-redirect-from (= 0.13.0) + jekyll-relative-links (= 0.5.3) + jekyll-remote-theme (= 0.2.3) + jekyll-sass-converter (= 1.5.2) + jekyll-seo-tag (= 2.4.0) + jekyll-sitemap (= 1.2.0) + jekyll-swiss (= 0.4.0) + jekyll-theme-architect (= 0.1.1) + jekyll-theme-cayman (= 0.1.1) + jekyll-theme-dinky (= 0.1.1) + jekyll-theme-hacker (= 0.1.1) + jekyll-theme-leap-day (= 0.1.1) + jekyll-theme-merlot (= 0.1.1) + jekyll-theme-midnight (= 0.1.1) + jekyll-theme-minimal (= 0.1.1) + jekyll-theme-modernist (= 0.1.1) + jekyll-theme-primer (= 0.5.3) + jekyll-theme-slate (= 0.1.1) + jekyll-theme-tactile (= 0.1.1) + jekyll-theme-time-machine (= 0.1.1) + jekyll-titles-from-headings (= 0.5.1) + jemoji (= 0.9.0) + kramdown (= 1.16.2) + liquid (= 4.0.0) + listen (= 3.1.5) + mercenary (~> 0.3) + minima (= 2.4.0) + nokogiri (>= 1.8.1, < 2.0) + rouge (= 2.2.1) + terminal-table (~> 1.4) + github-pages-health-check (1.4.0) + addressable (~> 2.3) + net-dns (~> 0.8) + octokit (~> 4.0) + public_suffix (~> 2.0) + typhoeus (~> 1.3) + html-pipeline (2.7.1) + activesupport (>= 2) + nokogiri (>= 1.4) + http_parser.rb (0.6.0) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + jekyll (3.7.3) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 0.7) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 2.0) + kramdown (~> 1.14) + liquid (~> 4.0) + mercenary (~> 0.3.3) + pathutil (~> 0.9) + rouge (>= 1.7, < 4) + safe_yaml (~> 1.0) + jekyll-avatar (0.5.0) + jekyll (~> 3.0) + jekyll-coffeescript (1.1.1) + coffee-script (~> 2.2) + coffee-script-source (~> 1.11.1) + jekyll-commonmark (1.2.0) + commonmarker (~> 0.14) + jekyll (>= 3.0, < 4.0) + jekyll-commonmark-ghpages (0.1.5) + commonmarker (~> 0.17.6) + jekyll-commonmark (~> 1) + rouge (~> 2) + jekyll-default-layout (0.1.4) + jekyll (~> 3.0) + jekyll-feed (0.9.3) + jekyll (~> 3.3) + jekyll-gist (1.5.0) + octokit (~> 4.2) + jekyll-github-metadata (2.9.4) + jekyll (~> 3.1) + octokit (~> 4.0, != 4.4.0) + jekyll-mentions (1.3.0) + activesupport (~> 4.0) + html-pipeline (~> 2.3) + jekyll (~> 3.0) + jekyll-optional-front-matter (0.3.0) + jekyll (~> 3.0) + jekyll-paginate (1.1.0) + jekyll-readme-index (0.2.0) + jekyll (~> 3.0) + jekyll-redirect-from (0.13.0) + jekyll (~> 3.3) + jekyll-relative-links (0.5.3) + jekyll (~> 3.3) + jekyll-remote-theme (0.2.3) + jekyll (~> 3.5) + rubyzip (>= 1.2.1, < 3.0) + typhoeus (>= 0.7, < 2.0) + jekyll-sass-converter (1.5.2) + sass (~> 3.4) + jekyll-seo-tag (2.4.0) + jekyll (~> 3.3) + jekyll-sitemap (1.2.0) + jekyll (~> 3.3) + jekyll-swiss (0.4.0) + jekyll-theme-architect (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-cayman (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-dinky (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-hacker (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-leap-day (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-merlot (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-midnight (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-minimal (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-modernist (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-primer (0.5.3) + jekyll (~> 3.5) + jekyll-github-metadata (~> 2.9) + jekyll-seo-tag (~> 2.0) + jekyll-theme-slate (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-tactile (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-theme-time-machine (0.1.1) + jekyll (~> 3.5) + jekyll-seo-tag (~> 2.0) + jekyll-titles-from-headings (0.5.1) + jekyll (~> 3.3) + jekyll-watch (2.0.0) + listen (~> 3.0) + jemoji (0.9.0) + activesupport (~> 4.0, >= 4.2.9) + gemoji (~> 3.0) + html-pipeline (~> 2.2) + jekyll (~> 3.0) + kramdown (1.16.2) + liquid (4.0.0) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + mercenary (0.3.6) + mini_portile2 (2.3.0) + minima (2.4.0) + jekyll (~> 3.5) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + minitest (5.11.3) + multipart-post (2.0.0) + net-dns (0.8.0) + nokogiri (1.8.2) + mini_portile2 (~> 2.3.0) + octokit (4.8.0) + sawyer (~> 0.8.0, >= 0.5.3) + pathutil (0.16.1) + forwardable-extended (~> 2.6) + public_suffix (2.0.5) + rb-fsevent (0.10.3) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + rouge (2.2.1) + ruby-enum (0.7.2) + i18n + ruby_dep (1.5.0) + rubyzip (1.2.1) + safe_yaml (1.0.4) + sass (3.5.6) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sawyer (0.8.1) + addressable (>= 2.3.5, < 2.6) + faraday (~> 0.8, < 1.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + thread_safe (0.3.6) + typhoeus (1.3.0) + ethon (>= 0.9.0) + tzinfo (1.2.5) + thread_safe (~> 0.1) + unicode-display_width (1.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + github-pages + +BUNDLED WITH + 1.16.1 diff --git a/docs/_config.yaml b/docs/_config.yaml new file mode 100644 index 00000000..0333b947 --- /dev/null +++ b/docs/_config.yaml @@ -0,0 +1,2 @@ +theme: jekyll-theme-minimal +exclude: [ruby-bundle] diff --git a/docs/_data/index.yaml b/docs/_data/index.yaml new file mode 100644 index 00000000..978dc054 --- /dev/null +++ b/docs/_data/index.yaml @@ -0,0 +1,27 @@ + +# This data could theoretically be retrieved directly from the md files: +# https://jekyllrb.com/tutorials/navigation/#scenario-8-retrieving-items-based-on-front-matter-properties + +sections: + - title: For Users + docs: + - title: Quick Start Guide + url: quick-start + - title: ODrive Tool + url: odrivetool + - title: Parameters & Commands + url: commands + - title: Interfaces + url: commands + - title: Motors & Encoders + url: encoders + - title: Troubleshooting + url: troubleshooting + - title: For ODrive Developers + docs: + - title: Firmware Developer Guide + url: developer-guide + - title: Configuring Visual Studio Code + url: configuring-vscode + - title: Configuring Eclipse + url: configuring-eclipse diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html new file mode 100644 index 00000000..7db4f016 --- /dev/null +++ b/docs/_layouts/default.html @@ -0,0 +1,80 @@ + + + + + + + + +{% seo %} + + + + +
+
+

{{ site.title | default: site.github.repository_name }} Documentation

+ + {% if site.logo %} + Logo + {% endif %} + +

{{ site.description | default: site.github.project_tagline }}

+ +
    + {% for section in site.data.index.sections %} +
  • +

    {{ section.title }}

    + +
  • + {% endfor %} +
+ + {% if site.github.is_project_page %} +

View the Project on GitHub {{ site.github.repository_nwo }}

+ {% endif %} + + {% if site.github.is_user_page %} +

View My GitHub Profile

+ {% endif %} + + {% if site.show_downloads %} + + {% endif %} +
+
+ + {{ content }} + +
+ +
+ + {% if site.google_analytics %} + + {% endif %} + + + diff --git a/Firmware/ascii-protocol.md b/docs/ascii-protocol.md similarity index 100% rename from Firmware/ascii-protocol.md rename to docs/ascii-protocol.md diff --git a/docs/assets/css/style.scss b/docs/assets/css/style.scss new file mode 100644 index 00000000..acc4f228 --- /dev/null +++ b/docs/assets/css/style.scss @@ -0,0 +1,257 @@ +--- +--- + +$section-headings-color: #0086b3; + +@import "{{ site.theme }}"; + +/*** Unset all header navbar styling done by the theme ***/ +// This corresponds to: +// https://github.com/pages-themes/minimal/blob/master/_sass/jekyll-theme-minimal.scss#L114-L172 + +header ul { + list-style:unset; + height:unset; + padding:unset; + background:unset; + border-radius:unset; + border:unset; + width:unset; +} +header li { + width:unset; + float:unset; + border-right:unset; + height:unset; +} +header li:first-child a { + border-radius:unset; +} +header li:last-child a { + border-radius:unset; +} +header ul a { + line-height:unset; + font-size:unset; + color:unset; + display:unset; + text-align:unset; + padding-top:unset; + height:unset; +} +header ul a:hover, header ul a:focus { + color:unset; + font-weight:unset; +} +header ul a:active { + background-color:unset; +} +strong { + color:unset; + font-weight:unset; +} +header ul li + li + li { + border-right:unset; + width:unset; +} +header ul a strong { + font-size:unset; + display:unset; + color:unset; +} + + + + +/*** Overall layout ***/ + +.wrapper { + width: auto; +} + +section { + /* align-content: center; */ + padding-left: 270px; + width: 700px; + float: none; +} + +@media print, screen and (max-width: 960px) { + section { + padding: 20px 0; + } +} + +div { + text-align: justify; +} + + + +/*** Links ***/ +a { + color: #e70000; +} + +a:hover, a:focus { + color: #8b0000; + font-weight: unset; +} + +header > ul { + list-style:none; + padding-right: 20px; + //padding:0; + //width:240px; + //height:240px; +} + +header > ul ul { + margin:0; + margin-bottom:12px; +} + +// Note that the theme defines a "header ul li + li + li" +// selector which has higher specifity than this one so we +// we need the "important" keywords for some properties. +header li { + width:100% !important; + //float:left; + //border-bottom:1px solid #e0e0e0; + border:none !important; + //height:unset; + //height:20px; +} + +header ul ul li a { + background: #f4f4f4; + border:1px solid #e0e0e0; + line-height:1; + font-size:12px; + color:#676767; + display:block; + text-align:left; + padding:12px 0px 5px 5px; + //margin:12px; + height:20px; +} + +/*** Navbar Hover ***/ +header ul a:hover, header ul a:focus { + color:rgb(0, 0, 0); + background-color: rgba(0, 0, 0, 0.24); +} +header ul a:active { + background-color:#f0f0f0; +} + +header ul p { + margin:0; + padding-left:5px; + display: block; + color: black; + background-color: #00000034; +} + +/* +header li:first-child a { + border-radius:5px 5px 0 0; +} + +header li:last-child a { + border-radius:0 0 5px 5px; +} + +header ul a { + background: #f4f4f4; + border:1px solid #e0e0e0; + line-height:1; + font-size:12px; + color:#676767; + display:block; + text-align:left; + padding:12px 0px 5px 5px; + //margin:12px; + height:20px; +} + +// header ul li + li + li { +// border-right:unset; +// border:1px solid #e0e0e0; +// width:100%; +// } + + */ + + + +/*** Keyboard symbols ***/ + +kbd { + display: inline-block; + padding: 3px 5px; + font-size: 11px; + line-height: 10px; + color: #444d56; + vertical-align: middle; + background-color: #fafbfc; + border: solid 1px #c6cbd1; + border-bottom-color: #959da5; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #959da5; +} + +p + details { + margin-top: -14px; +} + +/*** Expandable details box ***/ + +details { + //margin-top: -14px; + margin-bottom: 5px; + //background-color: #ffc54727; + background-color: #f2f8fd; + //border: solid 1px #c6cbd1; + border-radius: 3px; +} + +// title of a "details" box +details summary { + padding-left: 8px; + background-color: #b3d4f080; + //background-color: #ffc54727; + //border-bottom: solid 1px #c6cbd1; + //border-radius: 3px; +} + +// content of a "details" box +details div { + padding-top: 5px; + padding-left: 8px; + padding-right: 8px; + padding-bottom: 8px; + border-top: solid 1px #c6cbd1; +} +details div > p:last-child { + margin-bottom: 0px; +} + +/*** Alert box ***/ + +.alert { + padding: 10px; + color: black; + background-color: #ffc40096; + margin-bottom: 15px; + //border: solid 1px #000000; +} + +.alert * { + color: black; +} + +/*** inline code ***/ +:not(pre) > code { + background-color: #ededed; +} diff --git a/docs/commands.md b/docs/commands.md new file mode 100644 index 00000000..f57d0765 --- /dev/null +++ b/docs/commands.md @@ -0,0 +1,71 @@ +# Parameters & Commands + +Each axis can be controlled independently. +Most parameters can be set per axis. They are referred to as `` + +### State Machine + +Each axis has an independent state machine. The default state after startup is `AXIS_STATE_IDLE`. The current state is indicated by `.current_state`. The user can request a new state by assigning a new value to `.requested_state`. + + 1. `AXIS_STATE_IDLE` Disable motor PWM and do nothing. + 1. `AXIS_STATE_STARTUP_SEQUENCE` Run the [startup procedure](#startup-procedure). + 1. `AXIS_STATE_FULL_CALIBRATION_SEQUENCE` Run motor calibration and then encoder offset calibration (or encoder index search if `.encoder.use_index` is `True`). + 1. `AXIS_STATE_MOTOR_CALIBRATION` Measure phase resistance and phase inductance. + * To store the results set `.motor.config.pre_calibrated` to `True` and [save the configuration](#saving-the-configuration). After that you don't have to run the motor calibration on the next start up. + * This modifies the variables `.motor.config.phase_resistance` and `.motor.config.phase_inductance`. + 1. `AXIS_STATE_SENSORLESS_CONTROL` Run sensorless control. + * The motor must be calibrated (`.motor.is_calibrated`) + * [`.controller.control_mode`](#control-mode) must be `True`. + 1. `AXIS_STATE_ENCODER_INDEX_SEARCH` Turn the motor in one direction until the encoder index is traversed. This state can only be entered if `.encoder.config.use_index` is `True`. + 1. `AXIS_STATE_ENCODER_OFFSET_CALIBRATION` Turn the motor in one direction for a few seconds and then back to measure the offset between the encoder position and the electrical phase. + * Can only be entered if the motor is calibrated (`.motor.is_calibrated`). + * A successful encoder calibration will make the `.encoder.is_ready` go to true. + 1. `AXIS_STATE_CLOSED_LOOP_CONTROL` Run closed loop control. + * The action depends on the [control mode](#control-mode). + * Can only be entered if the motor is calibrated (`.motor.is_calibrated`) and the encoder is ready (`.encoder.is_ready`). + +### Startup Procedure + +By default the ODrive takes no action at startup and goes to idle immediately. +This behavior can be changed by modifying the following parameters: + +* `.config.startup_motor_calibration` +* `.config.startup_encoder_index_search` +* `.config.startup_encoder_offset_calibration` +* `.config.startup_closed_loop_control` +* `.config.startup_sensorless_control` + +See [state machine](#state-machine) for a description of each state. + +### Control Mode +By default both motors are enabled, and the default control mode is position control. +If you want a different mode, you can change `odrv0.motorN.config.control_mode`. +Possible values are: +* `CTRL_MODE_POSITION_CONTROL` +* `CTRL_MODE_VELOCITY_CONTROL` +* `CTRL_MODE_CURRENT_CONTROL` +* `CTRL_MODE_VOLTAGE_CONTROL` - this one is not normally used. + +To disable a motor at startup, set `odrv0.axisN.config.enable_control` and `odrv0.axisN.config.do_calibration` to `False`. + +### Tuning parameters +The motion control gains are currently manually tuned: +* `odrv0.motorN.config.pos_gain = 20.0f` [(counts/s) / counts] +* `odrv0.motorN.config.vel_gain = 15.0f / 10000.0f` [A/(counts/s)] +* `odrv0.motorN.config.vel_integrator_gain = 10.0f / 10000.0f` [A/((counts/s) * s)] + +An upcoming feature will enable automatic tuning. Until then, here is a rough tuning procedure: +* Set the integrator gain to 0 +* Make sure you have a stable system. If it is not, decrease all gains until you have one. +* Increase `vel_gain` by around 30% per iteration until the motor exhibits some vibration. +* Back down `vel_gain` to 50% of the vibrating value. +* Increase `pos_gain` by around 30% per iteration until you see some overshoot. +* Back down `pos_gain` until you do not have overshoot anymore. +* The integrator is not easily tuned, nor is it strictly required. Tune at your own discression. + +### Saving the configuration + +All variables that are part of a `[...].config` object can be saved to non-volatile memory on the ODrive so they persist after you remove power. The relevant commands are: + + * `odrv.save_configuration()`: Stores the configuration to persistent memory on the ODrive. + * `odrv.erase_configuration()`: Resets the configuration variables to their factory defaults. This only has an effect after a reboot. A side effect of this command is that motor control stops (in case it was running) and the USB communication breaks out temporarily. This is because erasing flash pages hangs the microcontroller for several seconds. diff --git a/Firmware/configuring-eclipse.md b/docs/configuring-eclipse.md similarity index 100% rename from Firmware/configuring-eclipse.md rename to docs/configuring-eclipse.md diff --git a/Firmware/configuring-vscode.md b/docs/configuring-vscode.md similarity index 92% rename from Firmware/configuring-vscode.md rename to docs/configuring-vscode.md index add9ff45..3956174f 100644 --- a/Firmware/configuring-vscode.md +++ b/docs/configuring-vscode.md @@ -1,4 +1,4 @@ -# Configuring VSCode +# Configuring Visual Studio Code VSCode is the recommended IDE for working with the ODrive codebase. It is a light-weight text editor with Git integration and GDB debugging functionality. @@ -33,7 +33,7 @@ A terminal window will open with your native shell. VSCode is configured to run A terminal window will open with your native shell. VSCode is configured to run the command `make flash` in this terminal. -If the flashing worked, you can start sending commands. If you want to do that now, you can go to [Communicating over USB or UART](README.md#communicating-over-usb-or-uart). +If the flashing worked, you can connect to the board using the [odrivetool](../docs/quick-start#start-odrivetool). ## Debugging An extension called Cortex-Debug has recently been released which is designed specifically for debugging ARM Cortex projects. You can read more on Cortex-Debug here: https://github.com/Marus/cortex-debug diff --git a/docs/developer-guide.md b/docs/developer-guide.md new file mode 100644 index 00000000..af7fe90a --- /dev/null +++ b/docs/developer-guide.md @@ -0,0 +1,228 @@ +# 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](CHANGELOG.md) to keep track of updates. + +### Table of contents + + + +- [Prerequisites](#prerequisites) +- [Configuring the build](#configuring-the-build) +- [Building and flashing the Firmware](#building-and-flashing-the-firmware) +- [Debugging](#debugging) +- [Setting up an IDE](#setting-up-an-ide) +- [STM32CubeMX](#stm32cubemx) +- [Troubleshooting](#troubleshooting) +- [Notes for Contributors](#notes-for-contributors) + + + +

+## Prerequisites + +The recommended tools for ODrive development are: + + * **make**: Used to invoke tup + * **Tup**: The build system used to invoke the compile commands + * **ARM GNU Compiler**: For cross-comiling code + * **ARM GDB**: For debugging the code and stepping through on the device + * **OpenOCD**: For flashing the ODrive with the STLink/v2 programmer + * **Python**: For running the Python tools + +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: +```bash +$ 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) +```bash +sudo apt-get install gcc-arm-none-eabi +sudo apt-get install gdb-arm-none-eabi +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 +``` +* [OpenOCD AUR package](https://aur.archlinux.org/packages/openocd/) + +#### Mac +```bash +brew cask install gcc-arm-embedded +brew cask install osxfuse && brew install tup +brew install openocd +``` + +#### Windows +__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](https://git-scm.com/download/win) bash. + +* [ARM compiler](https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads) +* [Tup](http://gittup.org/tup/index.html) +* [Make for Windows](http://gnuwin32.sourceforge.net/packages/make.htm) +* [OpenOCD](http://gnuarmeclipse.github.io/openocd/install/). Also follow the instructions on the ST-LINK/V2 drivers. + +
+ +## 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` or `v3.4-48V`. Check for a label on the upper side of the ODrive to find out which version you have. + +__CONFIG_USB_PROTOCOL__: Defines which protocol the ODrive should use on the USB interface. + * `native`: The native ODrive protocol. Use this if you want to use the python tools in this repo. + * `native-stream`: Like the native ODrive protocol, but the ODrive will treat the USB connection exactly as if it was a UART connection. __Use this if you're on macOS__. This is necessary because macOS doesn't grant our python tools sufficient low-level access to treat the device as the USB device that it is. + * `ascii`: The ASCII protocol. This allows sending simple commands like position setpoints directly from the terminal to the ODrive without the use of intermediate utilities. + * `none`: Disable USB. The device will still show up when plugged in but it will ignore any commands. + +__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. + * `native`: The native ODrive protocol. Use this if you're connecting the ODrive to a PC using UART and want to use the python tools to control and setup the ODrive. + * `ascii`: The ASCII protocol. Use this option if you control the ODrive with an Arduino. The ODrive Arduino library is not yet updated to the native protocol. + * `none`: Disable UART. + +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`](odrivetool#device-firmware-update). + +### Flashing using an STLink/v2 programmer + +* Connect `GND`, `SWD`, and `SWC` on connector J2 to the programmer. Note: Always plug in `GND` first! +* You need to power the board by only **ONE** of the following: VCC(3.3v), 5V, or the main power connection (the DC bus). The USB port (J1) does not power the board. +* Run `make flash` in the `Firmware` directory. + +If the flashing worked, you can connect to the board using the [odrivetool](quick-start#start-odrivetool). + +

+## Debugging +* Run `make gdb`. This will reset and halt at program start. Now you can set breakpoints and run the program. If you know how to use gdb, you are good to go. + +

+## 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: + +* [Configuring VSCode](configuring-vscode.md) +* [Configuring Eclipse](configuring-eclipse.md) + +

+## STM32CubeMX + +This project uses the STM32CubeMX tool to generate startup code and to ease the configuration of the peripherals. You can download it from [here](http://www2.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-configurators-and-code-generators/stm32cubemx.html?icmp=stm32cubemx_pron_pr-stm32cubef2_apr2014&sc=stm32cube-pr2). All CubeMX related files are in `Firmware/Board/v3`. + +You will likely want the pinout for this process. It is available [here](https://docs.google.com/spreadsheets/d/1QXDCs1IRtUyG__M_9WruWOheywb-GhOwFtfPcHuN2Fg/edit#gid=404444347). + +### Generate code +* Run stm32cubeMX and load the `Firmware/Board/v3/Odrive.ioc` project file. +* Press `Project -> Generate code` +* You may need to let it download some drivers and such. +* After generating/updating the code, some minor patches need to be applied. To do this, run: + `git apply Firmware/Board/v3/*.patch` +* Run `git config --local core.autocrlf input`. This will tell git that all files should be checked in with LF endings (CubeMX generates CRLF endings). +* `git status` will still claim that many files are modified but the actual diff (using `git diff`) is empty (apart from all the line ending warnings). + +### Generating patchfiles +If you made changes to CubeMX generated files outside of the `USER CODE BEGIN`...`USER CODE END` sections and contribute them back, please add a patch file so that the next person who runs CubeMX doesn't run into problems. + +CubeMX will reset everything outside these sections to the original state; we will capturing into a patch file the changes required to undo this resetting. +* Make sure your current desired state is committed. +* Make a new temporary branch: `git checkout -b cubemx_temp` +* Run the CubeMX code generation as described in the previous section, including applying previous patches. +* The diff will now _not_ be empty since CubeMX reset your changes. +* Stage this state and commit it with a message like "CubeMX reset my changes". +* Run `git revert HEAD` to undo the resetting action CubeMX's regeneration had. This is the commit which you will export, so write a meaningful commit message. +* Run `git format-patch HEAD~1` to export the commit as patch file. +* Check out your previous branch and then force-delete the temporary branch: `git branch -D cubemx_temp` +* Move the patch file to `Firmware/Board/v3/` and add it in a new commit. + +

+## Troubleshooting + +### `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 + http://openocd.org/doc/doxygen/bugs.html +Info : auto-selecting first available session transport "hla_swd". To override use 'transport select '. +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 + +## Documentation + +All *.md files in the `docs/` directory of the master branch are served up by GitHub Pages on [this domain](https://docs.odriverobotics.com). + + * Theme: [minimal](https://github.com/pages-themes/minimal) by [orderedlist](https://github.com/orderedlist) + * HTML layout: `docs/_layouts/default.html` + * CSS style: `docs/assets/css/styles.scss` + * Site index: `docs/_data/index.yaml` + +To run the docs server locally: + +```bash +cd docs +gem install bundler +bundle install --path ruby-bundle +bundle exec jekyll serve +``` + +## Releases + +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](https://google.github.io/styleguide/cppguide.html), except that the default indendtation is 4 spaces, and that the 80 character limit is not very strictly enforced, merely encouraged. \ No newline at end of file diff --git a/docs/encoders.md b/docs/encoders.md new file mode 100644 index 00000000..5e88b9e5 --- /dev/null +++ b/docs/encoders.md @@ -0,0 +1,26 @@ +# Motors & Encoders + +[TODO: UPDATE] + +## Encoder Calibration +By default the encoder-to-motor calibration will run on every startup. During encoder calibration the rotor must be allowed to rotate without any biased load during startup. That means mass and weak friction loads are fine, but gravity or spring loads are not okay. + +### Encoder with Index signal +If you have an encoder with an index (Z) signal, you may avoid having to do the calibration on every startup, and instead use the index signal to re-sync the encoder to a stored calibration. Below are the steps to do the one-time calibration and configuration. Note that you can follow these steps with one motor at a time, or all motors together, as you wish. + +* Since you will only do this once, it is recommended that you mechanically disengage the motor from anything other than the encoder, so it can spin freely. +* All the parameters we will be modifying are in the motor structs at the top of [MotorControl/low_level.c](MotorControl/low_level.c). +* Set `.encoder.use_index = true` and `.encoder.calibrated = false`. +* Flash this configuration, and let the motor scan for the index pulse and then complete the encoder calibration. +* Enter the following to print out the calibration parameters (substitute the motor number you are calibrating for ``): + * `odrv0.motor.encoder.encoder_offset` - This should print a number, like -326 or 1364. + * `odrv0.motor.encoder.motor_dir` - This should print 1 or -1. +* Copy these numbers to the corresponding entries in low_level.c: `.encoder.encoder_offset` and `.encoder.motor_dir`. + * _Warning_: Please be careful to enter the correct numbers, and not to confuse the motor channels. Incorrect values may cause the motor to spin out of control. +* Set `.encoder.calibrated = true`. +* Flash this configuration and check that the motor scans for the index pulse but skips the encoder calibration. +* Congratulations, you are now done. You may now attach the motor to your mechanical load. + + +* If you wish to scan for the index pulse in the other direction (if for example your axis usually starts close to a hard-stop), you can set a negative value in `.encoder.config.idx_search_speed`. +* If your motor has problems reaching the index location due to the mechanical load, you can increase `.encoder.calibration_current`. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..58622112 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,6 @@ + +{% for section in site.data.index.sections %} +## {{ section.title }} +{% for item in section.docs %} + * [{{ item.title }}]({{ item.url }}) {% endfor %} +{% endfor %} diff --git a/docs/interfaces.md b/docs/interfaces.md new file mode 100644 index 00000000..0f495f7d --- /dev/null +++ b/docs/interfaces.md @@ -0,0 +1,68 @@ +# Interfaces + +**Warning:** While developing custom ODrive control code it is recommend that your motors are free to spin continuously and are not connected to a drivetrain with limited travel. + +The ODrive can be controlled over various interfaces and protocols. + +[TODO: include a picture that shows all interfaces with the supported protocols] + +### Setting up UART +Baud rate: 115200 +Pinout: +* GPIO 1: Tx (connect to Rx of other device) +* GPIO 2: Rx (connect to Tx of other device) +* GND: you must connect the grounds of the devices together. Use any GND pin on J3 of the ODrive. + +## Native protocol + +If you have a choice, this is the recommended protocol for all applications. + +### Python + +The ODrive tools you installed earlier come with a library that you can use to easily control the ODrive from Python. + +Assuming you already installed the odrive library (`pip install odrive`), the simplest program to control the ODrive is this: + +``` +import odrive.discovery +odrv0 = odrive.discovery.find_any() +print(str(odrv0.vbus_voltage)) +``` + +For a more detailed example, see [odrive_demo.py](tools/odrive_demo.py). + +### Other languages + +We don't have an official library for you just yet. Check the community, there might be someone working on it. If you want to write a library yourself, refer to the [native protocol specification](protocol.md). You are of course welcome to contribute it back. + +## ASCII protocol + +This is a simpler alternative to the native protocol if you don't need all its bells and whistles. Before you use this, be sure that you're ok with its limitations. + +This protocol may be extended in the future to support a selected set of GCode commands. + +For more details, see the [ASCII protocol specification](ascii-protocol.md). + +### C++ (Arduino) + +[See ODrive Arduino Library](https://github.com/madcowswe/ODriveArduino) + +## Step/direction +This is the simplest possible way of controlling the ODrive. It is also the most primitive and brittle one. So don't use it unless you must interoperate with other hardware that you don't control. + +Pinout: +* GPIO 1: M0 step +* GPIO 2: M0 dir +* GPIO 3: M1 step +* GPIO 4: M1 dir +* GND: you must connect the grounds of the devices together. Use any GND pin on J3 of the ODrive. + +Please note that GPIO_3 and GPIO_4 are NOT 5v tolerant on ODrive v3.2 and earlier, so 3.3V signals only! +ODrive v3.3 and onward have 5V tolerant GPIO pins. + +To enable step/dir mode for the GPIO, please see [Setting the GPIO mode](Firmware/README.md#communication-configuration). + +There is also a config variable called `counts_per_step`, which specifies how many encoder counts a "step" corresponds to. It can be any floating point value. +The maximum step rate is pending tests, but it should handle at least 16kHz. If you want to test it, please be aware that the failure mode on too high step rates is expected to be that the motors shuts down and coasts. + +Please be aware that there is no enable line right now, and the step/direction interface is enabled by default, and remains active as long as the ODrive is in position control mode. By default the ODrive starts in position control mode, so you don't need to send any commands over USB to get going. You can still send USB commands if you want to. diff --git a/docs/odrivetool.md b/docs/odrivetool.md new file mode 100644 index 00000000..f1bb3056 --- /dev/null +++ b/docs/odrivetool.md @@ -0,0 +1,23 @@ +# ODrive Tool + +### Multiple ODrives +By default, `odrivetool` will connect to any ODrive it finds. If this is not what you want, you can select a specific ODrive. + +If you have multiple ODrives connected, you should specify which one to connect to. +* Run `(lsusb -d 1209:0d32 -v; lsusb -d 0483:df11 -v) 2>/dev/null | grep iSerial` to list the serial number of all flashable devices. Example output: +``` + iSerial 3 385F324D3037 + iSerial 3 306A396A3235 +``` +* The last column is the serial number you're looking for. You can unplug selected devices to track down the one you want to update. +* Run `odrivetool --serial-number=385F324D3037`, where `385F324D3037` is the targeted serial number. +* To connect over serial, run `odrivetool --path serial`. + +## Device Firmware Update + +Note: ODrive v3.4 and earlier require you to flash with the external programmer first (see below), before you can reflash in standalone mode. + +* Run `make dfu` in the `Firmware` directory. +* __Windows__: During the update, a new device called "STM32 BOOTLOADER" will appear. Open the [Zadig](http://zadig.akeo.ie/) utility and set the driver for "STM32 BOOTLOADER" to libusb-win32. After that the firmware update will continue. +* 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. +* Currently a firmware update will preserve the configuration if and only if the parameters of both firmware versions are identical. This will change in the future. diff --git a/docs/quick-start.md b/docs/quick-start.md new file mode 100644 index 00000000..15934295 --- /dev/null +++ b/docs/quick-start.md @@ -0,0 +1,169 @@ +# Quick Start Guide + +### Table of contents + + + +- [Wiring up the ODrive](#wiring-up-the-odrive) +- [Downloading and Installing Tools](#downloading-and-installing-tools) +- [Start `odrivetool`](#start-odrivetool) +- [Configure M0](#configure-m0) +- [Position control of M0](#position-control-of-m0) +- [What's next?](#whats-next) + + + +## Wiring up the ODrive + +
+Make sure you have a good mechanical connection between the encoder and the motor, slip can cause disasterous oscillations. +
+ +All non-power I/O is 3.3V output and 5V tolerant on input, except: +
__ODrive v3.2__: GPIO 3 and GPIO 4 are __not__ 5V tolerant.
+ +You will need: + +* One or two [brushless motors](https://hackaday.io/project/11583-odrive-high-performance-motor-control/log/37666-hobby-motors-in-your-robots). It is fine, even recommended, to start testing with just a single motor and encoder. +* One or two [quadrature incremental encoder(s)](https://discourse.odriverobotics.com/t/which-encoders-to-choose/63/2) +* A power resistor. A good starting point would be a [0.47 ohm, 50W resistor](https://www.digikey.com/product-detail/en/te-connectivity-passive-product/HSA50R47J/A102181-ND/2056131) + +
Do I really need a power resistor? What values to choose?
+ + If you don't have a brake resistor, the ODrive will pump excess power back into the power supply during deceleration to achieve the desired deceleration torque. If your power supply doesn't eat that power (which it won't if it's not a battery), the bus voltage will inevitebly rise. If you're unlucky this will break the power supply. + At some point, the ODrive's overvoltage protection will trip, after which both motors will be allowed to spin freely. Depending on your machine, this may or may not be a problem. + + The power resistor values you need depends on your motor setup, and peak/average deceleration power. + + To be on the safe side, think about what speed and current limits you want to set for the motor. + + When braking at max speed and with maximum motor current, the power that is dissipated in the power resistor can be calulated as: `P_brake = V_emf * I_motor` where `V_emf = motor_kv * V_bus`. + +
+ +* A power supply (12V-24V for the 24V board variant, 12V-48V for the 48V board variant). A battery is also fine. + +1. Wire up the motor phases into the 3-phase screw terminals, and the power resistor to the AUX terminal. Wire up the power source to the DC terminal, make sure to pay attention to the polarity. Do not apply power just yet. + +2. Wire up 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) + +## Downloading and Installing Tools + +Most instructions in this guide refer to a utility called `odrivetool`, so you should install that first. + +### Windows + +1. Install Python 3. We recommend the Anaconda distribution because it packs a lot of useful scientific tools, however you can also install the standalone python. + * __Anaconda__: Download the installer from [here](https://www.anaconda.com/download/#windows). Execute the downloaded file and follow the instructions. + * __Standalone Python__: Download the installer from [here](https://www.python.org/downloads/). Execute the downloaded file and follow the instructions. + * If you have Python 2 installed alongside Python 3, replace `pip` by `C:\Users\YOUR_USERNAME\AppData\Local\Programs\Python\Python36-32\Scripts\pip`. If you have trouble with this step then refer to [this walkthrough](https://www.youtube.com/watch?v=jnpC_Ib_lbc). +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 +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. + * If 'Odrive version 3.x' is not in the list of devices upon opening Zadig, check 'List All Devices' from the options menu. With the Odrive selected in the device list choose 'libusb-win32' from the target driver list and select the large 'install driver' button. + + +### Linux/macOS + +1. [Install Python 3](https://www.python.org/downloads/). +2. Install the ODrive tools by opening a terminal and typing `pip install odrive` Enter +3. __Linux__: set up USB permissions +``` + echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="0d[0-9][0-9]", MODE="0666"' | sudo tee /etc/udev/rules.d/50-odrive.rules + sudo udevadm control --reload-rules + sudo udevadm trigger # until you reboot you may need to do this everytime you reset the ODrive +``` + +## Start `odrivetool` +To launch the main interactive ODrive tool, type `odrivetool` Enter. Connect your ODrive and wait for the tool to find it. Now you can for instance type `odrv0.vbus_voltage` Enter to inpect the boards main supply voltage. +It should look something like this: + +``` +ODrive control utility v0.4.0 +Please connect your ODrive. +Type help() for help. + +Connected to ODrive 306A396A3235 as odrv0 +In [1]: odrv0.vbus_voltage +Out[1]: 11.97055721282959 +``` + +The tool you're looking at is a fully capable Python command prompt, so you can type any valid python code. + +## Configure M0 + +
Read this section carefully, else you risk breaking something.
+ +1. Set the limits: + +
Wait, how do I set these?
+ + In the previous step we started `odrivetool`. In there, you can assign variables directly by name. + + For instance, to set the current limit of M0 to 10A you would type: `odrv0.axis0.motor.config.current_lim = 10` Enter + +
+ + * The current limit: `odrv0.axis0.motor.config.current_lim` [A]. The default current limit, for safety reasons, is set to 10A. This is quite weak, and good for making sure the drive is stable. Once you have tuned the drive, you can increase this to 75A to get some performance. Note that above 75A, you must change the current amplifier gains. + * Note: The motor current and the current drawn from the power supply is not the same in general. You should not look at the power supply current to see what is going on with the motor current. + * The velocity limit: `odrv0.axis0.motor.config.vel_limit` [counts/s]. The motor will be limited to this speed; again the default value is quite slow. + * You can change `odrv0.axis0.motor.config.calibration_current` [A] to the largest value you feel comfortable leaving running through the motor continously when the motor is stationary. + +2. Set other hardware parameters: + + * `odrv0.config.brake_resistance` [Ohm]: This is the resistance of the brake resistor. If you are not using it, you may set it to `0`. + * `odrv0.axis0.motor.config.pole_pairs`: This is the number of **magnet poles** in the rotor, **divided by two**. You can simply count the number of permanent magnets in the rotor, if you can see them. _Note: this is not the same as the number of coils in the stator._ + * `odrv0.axis0.motor.config.motor_type`: This is the type of motor being used. Currently two types of motors are supported: High-current motors (`MOTOR_TYPE_HIGH_CURRENT`) and Gimbal motors (`MOTOR_TYPE_GIMBAL`). + +
Which `motor_type` to choose?
+ + If you're using a regular hobby brushless motor like [this](https://hobbyking.com/en_us/turnigy-aerodrive-sk3-5065-236kv-brushless-outrunner-motor.html) one, you should set `motor_mode` to `MOTOR_TYPE_HIGH_CURRENT`. For low-current gimbal motors like [this](https://hobbyking.com/en_us/turnigy-hd-5208-brushless-gimbal-motor-bldc.html) one, you should choose `MOTOR_TYPE_GIMBAL`. Do not use `MOTOR_TYPE_GIMBAL` on a motor that is not a gimbal motor, as it may overheat the motor or the ODrive. + + **Further detail:** + If 100's of mA of current noise is "small" for you, you can choose `MOTOR_TYPE_HIGH_CURRENT`. + If 100's of mA of current noise is "large" for you, and you do not intend to spin the motor very fast (omega * L << R), and the motor is fairly large resistance (1 ohm or larger), you can chose `MOTOR_TYPE_GIMBAL`. + If 100's of mA current noise is "large" for you, _and_ you intend to spin the motor fast, then you need to replace the shunt resistors on the ODrive. + +
+ + * `odrv0.axis0.encoder.config.cpr`: Encoder Count Per Revolution (CPR). This is 4x the Pulse Per Revolution (PPR) value. Usually this is indicated in the datasheet of your encoder. + +## 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 replace read "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. + +
What's the point of this?
+ This procedure first measures your motor's electrical properties (namely phase resistance and phase inductance) and then the offset between the motor's electrical phase and the encoder position. + +
+ + The startup procedure is demonstrated [here](https://www.youtube.com/watch?v=VCX1bA2xnuY). + + Note: the rotor must be allowed to rotate without any biased load during startup. That means mass and weak friction loads are fine, but gravity or spring loads are not okay. Also note that in the video, the motors spin after initalisation, but in the current software the default behaviour is to do position control to position 0 (i.e. the position at startup) + +
My motor doesn't beep or doesn't turn
+ + Make sure the motor wires are connected firmly. Check the value of `odrv0.axis0.error` and then refer to the [error code documentation](troubleshooting.md#error-codes) for details. + + Once you have understood the error and fixed its cause, you may clear the error state (`odrv0.axis0.error = 0` Enter) and retry. You may also need to clear the error state of other subcomponents (e.g. `odrv0.axis0.motor.error`). + +
+ +3. Type `odrv0.axis0.motor.config.pre_calibrated = True` Enter and then `odrv0.save_configuration()` Enter. This will save all the configuration and calibration you just did so the next time you start the device it's already ready to go. Except for one thing: you need to run the encoder offset calibration after every power cycle. +4. Type `odrv0.axis0.requested_state = AXIS_STATE_CLOSED_CONTROL_LOOP` Enter. From now on the ODrive will try to hold the motor's position. If you try to turn it by hand, it will fight you gently. That is unless you bump up `odrv0.axis0.motor.config.current_lim`, in which case it will fight you more fiercely. + +## What's next? + +You can now: + + * Set the [tuning parameters](commands#tuning-parameters) for better performance + * Control the ODrive from your own program or hook it up to an existing system through one of it's [interfaces](interfaces) + +If you have any issues or any questions please get in touch. The [ODrive Community](https://discourse.odriverobotics.com/) warmly welcomes you. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 00000000..956480fb --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,144 @@ +# Troubleshooting + +## Error codes +If your ODrive is not working as expected, run `odrivetool` and type `hex(odrv0.axis0.error)` Enter. This will display a [hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal) representation of the error code. Each bit represents one error flag. + +
Example
+ +Say you typed `hex(odrv0.axis0.error)` and got `0x6` as a result. Written in binary this number corresponds to `110`, so bits 1 and 2 are set. + +Looking at the reference below, this means that both `ERROR_DC_BUS_UNDER_VOLTAGE` and `ERROR_DC_BUS_OVER_VOLTAGE` occurred. + +
+ +There is a slight chance that the values here are out of sync with the actual firmware. To be completely sure, check the linked definition in the source code. + +### Axis error flags + +Defined [here](../Firmware/MotorControl/axis.hpp) + +0. `ERROR_INVALID_STATE` +1. `ERROR_DC_BUS_UNDER_VOLTAGE` +2. `ERROR_DC_BUS_OVER_VOLTAGE` +3. `ERROR_CURRENT_MEASUREMENT_TIMEOUT` +4. `ERROR_BRAKE_RESISTOR_DISARMED` +5. `ERROR_MOTOR_DISARMED` +6. `ERROR_MOTOR_FAILED` (check `.motor.error` for more details) +7. `ERROR_SENSORLESS_ESTIMATOR_FAILED` (check `.sensorless_estimator.error` for more details) +8. `ERROR_ENCODER_FAILED` (check `.encoder.error` for more details) +9. `ERROR_CONTROLLER_FAILED` +10. `ERROR_POS_CTRL_DURING_SENSORLESS` + +### Motor error flags + +Defined [here](MotorControl/motor.hpp) + +0. ERROR_PHASE_RESISTANCE_OUT_OF_RANGE +1. ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE +2. ERROR_ADC_FAILED +3. ERROR_DRV_FAULT +4. ERROR_CONTROL_DEADLINE_MISSED +5. ERROR_NOT_IMPLEMENTED_MOTOR_TYPE +6. ERROR_BRAKE_CURRENT_OUT_OF_RANGE +7. ERROR_NUMERICAL + +### Encoder error flags + +Defined [here](MotorControl/encoder.hpp) + +0. ERROR_NUMERICAL +1. ERROR_CPR_OUT_OF_RANGE +2. ERROR_RESPONSE + +### Sensorless estimator error flags + +Defined [here](MotorControl/sensorless_estimator.hpp) + +0. ERROR_NUMERICAL + + +### Cannot connect to the ODrive + +ensure no other ODrive program is running +prepend `PYUSB_DEBUG=debug` to the script + +## USB issues ## + +* Firmware: + 1. ODrive Firmware + 2. STM HAL code +* Electrical: + 1. ODrive hardware + 2. connection (cables, hub) +* PC side + 1. PC-side USB host controller + 2. kernel + 3. libusb driver + 4. libusb library + 5. PyUSB + 6. python code + + + +## DRV fault ## + +Hardware: 5330 (190kv), v3.4-48V, V_bus=12V +Settings: default gains, current_lim=50A, position control, stationary +Action: Applying a high torque manually +Result: Trips at I_q=45A (+- 2A) on the big motor, I_q=50A (+-1A) on the black motor, I_bus=8.3A (+-0.4), + +phase=-2.3 +-2.7 (did not trip from -0.5 to -2.7 @ 50A) +-2.8 +-2.7 +-2.4689412117004395 +0.7 +0.5 + +other odrive: +-0.8651647567749023 + +Repeatability: about 5/5 + + +``` +ODrive control utility v0.3.7.dev +Please connect your ODrive. +Type help() for help. + +Connected to ODrive 385F324D3037 as odrv1 +In [1]: odrv1.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION +In [2]: start_liveplotter(lambda: [odrv1.axis0.motor.current_control.Ibus, odrv1.axis0.motor.current_control.Iq_setpoint]) +In [4]: odrv1.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL +In [9]: odrv1.axis0.motor.config.current_lim=50 +In [11]: odrv1.axis0.controller.config.vel_limit +Out[11]: 20000.0 +In [12]: odrv1.axis0.controller.config.vel_gain +Out[12]: 0.0005000000237487257 +``` + + + +``` +In [9]: odrive.utils.print_drv_regs("M0",odrv1.axis0.motor) +M0: 0 +DRV Fault Code: 0 +Status Reg 1: 0 (0b00000000) +Status Reg 2: 1 (0b00000001) +Control Reg 1: 1360 (0b10101010000) +Control Reg 2: 8 (0b0001000) + +In [10]: odrive.utils.print_drv_regs("M1",odrv1.axis0.motor) +M1: 0 +DRV Fault Code: 0 +Status Reg 1: 0 (0b00000000) +Status Reg 2: 1 (0b00000001) +Control Reg 1: 1360 (0b10101010000) +Control Reg 2: 8 (0b0001000) + +In [11]: (hex(odrv1.axis0.error), hex(odrv1.axis0.motor.error)) +Out[11]: ('0x41', '0x18') + +In [12]: (hex(odrv1.axis1.error), hex(odrv1.axis1.motor.error)) +Out[12]: ('0x61', '0x10') +```