diff --git a/docs/commands.md b/docs/commands.md index 23e6e266..0ec87a36 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1,6 +1,6 @@ # Parameters & Commands -We will use the `` as a placeholder for any ODrive object. In `odrivetool` this is usually `odrv0`. Furthermore we use `` as a placeholder for any axis (for example `odrv0.axis0`). +We will use the `` as a placeholder for any ODrive object. Every ODrive controller is an ODrive object. In `odrivetool` this is usually `odrv0`. Furthermore we use `` as a placeholder for any axis, which is an attribute of an ODrive object (for example `odrv0.axis0`). An axis represents where the motors are connected. (axis0 for M0 or axis1 for M1) ## Per-Axis commands @@ -11,26 +11,27 @@ For the most part, both axes on the ODrive can be controlled independently. The current state of an axis is indicated by `.current_state`. The user can request a new state by assigning a new value to `.requested_state`. The default state after startup is `AXIS_STATE_IDLE`. 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. + 2. `AXIS_STATE_STARTUP_SEQUENCE` Run the [startup procedure](#startup-procedure). + 3. `AXIS_STATE_FULL_CALIBRATION_SEQUENCE` Run motor calibration and then encoder offset calibration (or encoder index search if `.encoder.use_index` is `True`). + 4. `AXIS_STATE_MOTOR_CALIBRATION` Measure phase resistance and phase inductance of the motor. * 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. + 5. `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. + 6. `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`. + 7. `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. + 8. `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 setting any of the following to `True`. The ODrive will sequence all enabled startup actions in the order shown below. +In order to change what startup procedures are used, set the startup procedures you want to `True`. +The ODrive will sequence all enabled startup actions selected in the order shown below. * `.config.startup_motor_calibration` * `.config.startup_encoder_index_search` @@ -49,6 +50,12 @@ Possible values are: * `CTRL_MODE_CURRENT_CONTROL` * `CTRL_MODE_VOLTAGE_CONTROL` - this one is not normally used. +# Control Commands + +* `.controller.pos_setpoint = ` +* `.controller.current_setpoint = ` +* `.controller.vel_setpoint = ` + ### Tuning parameters The motion control gains are currently manually tuned: * `.controller.config.pos_gain = 20.0f` [(counts/s) / counts] diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 52c85171..e0c45d42 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -110,7 +110,7 @@ You can also modify the compile-time defaults for all `.config` parameters. You 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). +3. Flash the firmware using [odrivetool dfu](odrivetool#device-firmware-update). ### Flashing using an STLink/v2 programmer diff --git a/docs/encoders.md b/docs/encoders.md index 9c3c52ba..47ec0944 100644 --- a/docs/encoders.md +++ b/docs/encoders.md @@ -5,11 +5,11 @@ Contributions to this table highly encouraged! Encoder|Manufacturer|Output|Index|Max CPR|Max RPM|Voltage|Supported|Price|Link|Datasheet :--|:--|:--|:-:|:-:|:-:|:-:|:--|:--|:--|:-- -AMT102|CUI|Quadrature|Yes|8192|-|5V|Yes|-|-|- -AMT203|CUI|Quadrature + Absolute|Yes|4096|-|5V|Quadrature Only|-|-|- -AS5047P|AMS|Quadrature + Absolute|Yes|4096|28k|3.3V or 5V|Quadrature Only|-|-|- -E6B2-CWZ6C|Omron|Quadrature|Yes|8000|-|5-24V|Yes|-|-|[Datasheet](http://www.ia.omron.com/products/family/487/) -J733|-|Quadrature|No|2400|-|5-24V|Yes|-|-|- +AMT102|CUI|Quadrature|Yes|8192 (2048 * 4)|-|5V|Yes|-|-|- +AMT203|CUI|Quadrature + Absolute|Yes|4096 (1024 * 4)|-|5V|Quadrature Only|-|-|- +AS5047P|AMS|Quadrature + Absolute|Yes|4096 (1024 * 4)|28k|3.3V or 5V|Quadrature Only|-|-|- +E6B2-CWZ6C|Omron|Quadrature|Yes|8000 (2000 * 4)|-|5-24V|Yes|-|-|[Datasheet](http://www.ia.omron.com/products/family/487/) +J733|-|Quadrature|No|2400 (600 * 4)|-|5-24V|Yes|-|-|- ## Encoder Calibration diff --git a/docs/getting-started.md b/docs/getting-started.md index 6b213970..22ed92da 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -77,28 +77,47 @@ Most instructions in this guide refer to a utility called `odrivetool`, so you s 3. Install dependencies by typing `pip install pywin32==222` 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. +5. Use the [Zadig](http://zadig.akeo.ie/) utility to set ODrive driver to libusb-win32. * Check 'List All Devices' from the options menu, and select 'ODrive 3.x Native Interface (Interface 2)'. With that selected in the device list choose 'libusb-win32' from the target driver list and then press the large 'install driver' button. ### OSX +We are going to run the following commands for installation in Terminal. 1. If you don't already have it, install homebrew: -```sh +```bash /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" ``` 2. Install python: -```sh +```bash brew install python ``` 3. If you get the error: `Error: python 2.7.14_2 is already installed`, then upgrade to Python 3 by running: -```sh +```bash brew upgrade python ``` -4. Now that you have Python 3 and all the package managers, run: -```sh +4. The odrive tools uses libusb to communicate to the ODrive: +```bash +brew install libusb +``` +5. Now that you have Python 3 and all the package managers, run: +```bash pip3 install odrive ``` +__Troubleshooting__ +1. Permission Errors: Just run the previous command in sudo +```bash +sudo pip3 install odrive +``` + +2. Dependency Errors: If the installer doesn't complete and you get a dependency +error (Ex. "No module..." or "module_name not found") +```bash +sudo pip3 install module_name +``` +Try step 5 again + + ### Linux 1. [Install Python 3](https://www.python.org/downloads/). @@ -112,7 +131,9 @@ pip3 install odrive ## Start `odrivetool` -
__ODrive v3.4 and earlier:__ Your board does not come preflashed with any firmware. Follow the instructions [here](odrivetool#device-firmware-update) before you continue.
+
__ODrive v3.5 and later:__ Your board should come preflashed with firmware. If you run into problems, follow the instructions [here](odrivetool#device-firmware-update) on the DFU procedure before you continue.
+ +
__ODrive v3.4 and earlier:__ Your board does __not__ come preflashed with any firmware. Follow the instructions [here](odrivetool#device-firmware-update) on the STP Link procedure before you continue.
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: @@ -152,7 +173,7 @@ You can read more about the odrivetool [here](odrivetool.md). The largest effect on modulation magnitude is speed. There are other smaller factors, but in general: if the motor is still it's not unreasonable to have 50A in the motor from 5A on the power supply. When the motor is spinning close to top speed, the power supply current and the motor current will be somewhat close to each other. - * 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. + * The velocity limit: `odrv0.axis0.controller.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: @@ -189,7 +210,6 @@ Let's get motor 0 up and running. The procedure for motor 1 is exactly the same,
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). @@ -197,15 +217,18 @@ Let's get motor 0 up and running. The procedure for motor 1 is exactly the same, **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 not like that.
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`). -
- 2. Type `odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL` 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. +3. Send the motor a new position setpoint. `odrv0.axis0.controller.pos_setpoint = 10000` Enter. The units are in encoder counts. + +### Other control modes +The ODrive also supports velocity control and current (torque) control. +* **Velocity control**: Set `odrv0.axis0.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL`. You can now control the velocity with `odrv0.axis0.controller.vel_setpoint = 5000`. Units are counts/s. +* **Current control**: Set `odrv0.axis0.controller.config.control_mode = CTRL_MODE_CURRENT_CONTROL`. You can now control the current with `odrv0.axis0.controller.vel_setpoint = 3`. Units are A. **NOTE**: There is no velocity limiting in current control mode. Make sure that you don't overrev the motor, or exceed the max speed for your encoder. ## What's next? diff --git a/docs/odrivetool.md b/docs/odrivetool.md index efa1f14b..d3c4b67a 100644 --- a/docs/odrivetool.md +++ b/docs/odrivetool.md @@ -15,7 +15,7 @@ By default, `odrivetool` will connect to any ODrive it finds. If this is not wha To find the serial number of your ODrive, run `odrivetool`, connect exactly one ODrive and power it up. You should see this: ``` Connected to ODrive 306A396A3235 as odrv0 -In [1]: +In [1]: ``` `306A396A3235` is the serial number of this particular ODrive. If you want ODrive Tool to ignore all other devices you would close it and then run `odrivetool --serial-number 306A396A3235`. @@ -45,7 +45,7 @@ You can use ODrive Tool to back up and restore device configurations or transfer To update the ODrive to the newest firmware release, simply open up a terminal and run the following command: ``` -~ $ odrivetool dfu +~ $ odrivetool dfu ODrive control utility v0.3.7.dev Waiting for ODrive... Found ODrive 308039673235 (v3.5-24V) with firmware v0.3.7-dev @@ -118,12 +118,12 @@ Info : using stlink api v2 Info : Target voltage: 3.236027 Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints adapter speed: 2000 kHz -target halted due to debug-request, current mode: Thread +target halted due to debug-request, current mode: Thread xPSR: 0x01000000 pc: 0x08009224 msp: 0x20020000 auto erase enabled Info : device id = 0x10076413 Info : flash size = 1024kbytes -target halted due to breakpoint, current mode: Thread +target halted due to breakpoint, current mode: Thread xPSR: 0x61000000 pc: 0x20000046 msp: 0x20020000 Warn : no flash bank found for address 10000000 wrote 262144 bytes from file ODriveFirmware_v3.4-24V.elf in 10.194110s (25.113 KiB/s) diff --git a/tools/odrive/configuration.py b/tools/odrive/configuration.py index eab40653..ff220a8d 100644 --- a/tools/odrive/configuration.py +++ b/tools/odrive/configuration.py @@ -3,7 +3,7 @@ import json import os import tempfile import fibre.remote_object -from odrive.utils import OperationAbortedException +from odrive.utils import OperationAbortedException, yes_no_prompt def get_dict(obj, is_config_object): result = {} @@ -51,7 +51,7 @@ def backup_config(device, filename, logger): logger.info("Saving configuration to {}...".format(filename)) if os.path.exists(filename): - if not odrive.utils.yes_no_prompt("The file {} already exists. Do you want to override it?".format(filename), True): + if not yes_no_prompt("The file {} already exists. Do you want to override it?".format(filename), True): raise OperationAbortedException() data = get_dict(device, False) diff --git a/tools/odrive/dfuse/DfuDevice.py b/tools/odrive/dfuse/DfuDevice.py index e7158d3b..be7d404e 100644 --- a/tools/odrive/dfuse/DfuDevice.py +++ b/tools/odrive/dfuse/DfuDevice.py @@ -156,12 +156,12 @@ class DfuDevice: self.set_address(addr) status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY) if status[1] != DfuState.DFU_DOWNLOAD_IDLE: - raise RuntimeError("An error occured. Device Status: %r" % status) + raise RuntimeError("An error occured. Device Status: {!r}".format(status)) # take device out of DFU_DOWNLOAD_SYNC and into DFU_IDLE self.abort() status = self.wait_while_state(DfuState.DFU_DOWNLOAD_SYNC) if status[1] != DfuState.DFU_IDLE: - raise RuntimeError("An error occured. Device Status: %r" % status) + raise RuntimeError("An error occured. Device Status: {!r}".format(status)) def erase_sector(self, sector): @@ -169,7 +169,7 @@ class DfuDevice: self.erase(sector['addr']) status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY, timeout=sector['len']/32) if status[1] != DfuState.DFU_DOWNLOAD_IDLE: - raise RuntimeError("An error occured. Device Status: %r" % status) + raise RuntimeError("An error occured. Device Status: {!r}".format(status)) def write_sector(self, sector, data): self.set_alternate_safe(sector['alt']) @@ -184,7 +184,7 @@ class DfuDevice: self.write(blocknum, block) status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY) if status[1] != DfuState.DFU_DOWNLOAD_IDLE: - raise RuntimeError("An error occured. Device Status: %r" % status) + raise RuntimeError("An error occured. Device Status: {!r}".format(status)) def read_sector(self, sector): """ diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index bc466f9c..0f5c58b8 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -69,10 +69,8 @@ def start_liveplotter(get_var_callback): while not cancellation_token.is_set(): plt.clf() plt.plot(vals) - if platform.system() == "Windows": - plt.pause(1/plot_rate) - else: - fig.canvas.flush_events() + fig.canvas.draw() + fig.canvas.start_event_loop(1/plot_rate) threading.Thread(target=fetch_data).start() threading.Thread(target=plot_data).start() diff --git a/tools/odrive_demo.py b/tools/odrive_demo.py index af7a203a..0e418495 100755 --- a/tools/odrive_demo.py +++ b/tools/odrive_demo.py @@ -19,18 +19,18 @@ my_drive = odrive.find_any() print("Bus voltage is " + str(my_drive.vbus_voltage) + "V") # Or to change a value, just assign to the property -my_drive.motor0.pos_setpoint = 3.14 -print("Position setpoint is " + str(my_drive.motor0.pos_setpoint)) +my_drive.axis0.controller.pos_setpoint = 3.14 +print("Position setpoint is " + str(my_drive.axis0.controller.pos_setpoint)) # And this is how function calls are done: -my_drive.motor0.set_pos_setpoint(0.0, 0.0, 0.0) +my_drive.axis0.controller.set_pos_setpoint(0.0, 0.0, 0.0) # A sine wave to test t0 = time.monotonic() while True: setpoint = 10000.0 * math.sin((time.monotonic() - t0)*2) print("goto " + str(int(setpoint))) - my_drive.motor0.set_pos_setpoint(setpoint, 0.0, 0.0) + my_drive.axis0.controller.set_pos_setpoint(setpoint, 0.0, 0.0) time.sleep(0.01)