From 7d62ea77c0af1b8a2bd077c68cb6cad305e83570 Mon Sep 17 00:00:00 2001 From: robopilot99 Date: Thu, 25 Jul 2019 23:03:52 -0500 Subject: [PATCH 1/2] Revise documentation to clarify tuning procedures --- docs/_data/index.yaml | 2 +- docs/commands.md | 20 ++------------------ docs/control.md | 21 +++++++++++++++++++++ docs/encoders.md | 6 +++--- docs/getting-started.md | 13 ++++++++----- docs/hoverboard.md | 9 +++++---- docs/interfaces.md | 8 ++++---- docs/odrivetool.md | 4 ++-- docs/troubleshooting.md | 4 ++-- 9 files changed, 48 insertions(+), 39 deletions(-) diff --git a/docs/_data/index.yaml b/docs/_data/index.yaml index 3ca1d28a..bd258a11 100644 --- a/docs/_data/index.yaml +++ b/docs/_data/index.yaml @@ -15,7 +15,7 @@ sections: url: interfaces - title: Encoders url: encoders - - title: Control + - title: Control & Tuning url: control - title: Hoverboard Guide url: hoverboard diff --git a/docs/commands.md b/docs/commands.md index a5f4675e..1dce7336 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -61,25 +61,9 @@ Possible values are: * `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.0` [(counts/s) / counts] -* `.controller.config.vel_gain = 5.0 / 10000.0` [A/(counts/s)] -* `.controller.config.vel_integrator_gain = 10.0 / 10000.0` [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 can be set to `0.5 * bandwidth * vel_gain`, where `bandwidth` is the overall resulting tracking bandwidth of your system. Say your tuning made it track commands with a settling time of 100ms: this means the bandwidth was 1/100ms or 10. In this case you should set the `vel_integrator_gain = 0.5 * 10 * vel_gain`. +* `.controller.current_setpoint = ` ## System monitoring commands @@ -89,7 +73,7 @@ An upcoming feature will enable automatic tuning. Until then, here is a rough tu ### Motor current and torque estimation * View the commanded motor current with `.motor.current_control.Iq_setpoint` [A] -* View the measured motor current with `.motor.current_control.Iq_measured` [A]. If you find that this returns noisy data then use the command motor current instead. The two values should be close so long as you are not approching the maximim achieveable rotational velocity of your motor for a given supply votlage, in which case the commanded current may become larger than the measured current. +* View the measured motor current with `.motor.current_control.Iq_measured` [A]. If you find that this returns noisy data then use the command motor current instead. The two values should be close so long as you are not approaching the maximum achievable rotational velocity of your motor for a given supply voltage, in which case the commanded current may become larger than the measured current. Using the motor current and the known KV of your motor you can estimate the motors torque using the following relationship: Torque [N.m] = 8.27 * Current [A] / KV. diff --git a/docs/control.md b/docs/control.md index fd04ac6d..adc1ba6e 100644 --- a/docs/control.md +++ b/docs/control.md @@ -4,6 +4,8 @@ The motor controller is a cascaded style position, velocity and current control ![Cascaded pos vel I loops](https://github.com/madcowswe/ODrive/blob/master/docs/controller_with_ff.png?raw=true) +Each stage of the control loop is a variation on a [PID controller](https://en.wikipedia.org/wiki/PID_controller). A PID controller is a mathematical model that can be adapted to control a wide variety of systems. This flexibility is essential as it allows the ODrive to be used to control all kinds of mechanical systems. + ### Position loop: The position controller is a P loop with a single proportional gain. ```text @@ -28,3 +30,22 @@ voltage_cmd = current_error * current_gain + voltage_integral (+ voltage_feedfor ``` For more detail refer to [controller.cpp](https://github.com/madcowswe/ODrive/blob/master/Firmware/MotorControl/controller.cpp#L86). +## Tuning +Tuning the motor controller is an essential step to unlock the full potential of the ODrive. Tuning allows for the controller to quickly respond to disturbances or changes in the system (such as an external force being applied or a change in the setpoint) without becoming unstable. Correctly setting the three tuning parameters (called gains) ensures that ODrive can control your motors in the most effective way possible. The three values are: +* `.controller.config.pos_gain = 20.0` [(counts/s) / counts] +* `.controller.config.vel_gain = 5.0 / 10000.0` [A/(counts/s)] +* `.controller.config.vel_integrator_gain = 10.0 / 10000.0` [A/((counts/s) * s)] + +An upcoming feature will enable automatic tuning. Until then, here is a rough tuning procedure: +* Set vel_integrator_gain 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 can be set to `0.5 * bandwidth * vel_gain`, where `bandwidth` is the overall resulting tracking bandwidth of your system. Say your tuning made it track commands with a settling time of 100ms (the time from when the setpoint changes to when the system arrives at the new setpoint); this means the bandwidth was 1/(100ms) = 1/(0.1s) = 10hz. In this case you should set the `vel_integrator_gain = 0.5 * 10 * vel_gain`. + +The liveplotter tool can be immensely helpful in dialing in these values. To display a graph that plots the position setpoint vs the measured position value run the following in the ODrive tool: + +`start_liveplotter(lambda:[odrv0.axis0.encoder.pos_estimate - odrv0.axis0 + ...: .controller.pos_setpoint])` diff --git a/docs/encoders.md b/docs/encoders.md index c3d42fb0..b8ab18ac 100644 --- a/docs/encoders.md +++ b/docs/encoders.md @@ -115,13 +115,13 @@ Connect to the I pin, see if you get a pulse on a complete rotation. Sometimes t If you are using SPI, have a lot at the signal on the CLK, and CS pins. There are many examples on the net for how these should behave. ## Encoder Noise -Noise is found in all circuits, life is just about figuring out if it is preventing your system from working. Lots of users have no problems with noise interferring with their odrive operation, others will tell you "_I've been using the same encoder as you with no problems_". Power to 'em, that may be true, but it doesn't mean it will work for you. If you are concerned about noise, there are several possible sources: +Noise is found in all circuits, life is just about figuring out if it is preventing your system from working. Lots of users have no problems with noise interfering with their odrive operation, others will tell you "_I've been using the same encoder as you with no problems_". Power to 'em, that may be true, but it doesn't mean it will work for you. If you are concerned about noise, there are several possible sources: * Importantly, encoder wires may be too close to motor wires, avoid overlap as much as possible * Long wires between encoder and ODrive * Use of ribbon cable -The following _might_ mitigate noise problems. Use shielded cable, or use twisted pairs, where one side of each twisted pair is tied to ground, the other side is tied to your signal. If you are using SPI, use a 20-50 ohm resistor in series on CLK, which is more susceptable noise. +The following _might_ mitigate noise problems. Use shielded cable, or use twisted pairs, where one side of each twisted pair is tied to ground, the other side is tied to your signal. If you are using SPI, use a 20-50 ohm resistor in series on CLK, which is more susceptible noise. If you are using an encoder with an index signal, another problem that has been encountered is with noise on the Z input of ODrive. Symptoms for this problem include: * difficulty with requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE, where your calibration sequence may not complete @@ -130,7 +130,7 @@ If you are using an encoder with an index signal, another problem that has been One easy step that _might_ fix the noise on the Z input has been to solder a 22nF-47nF capacitor to the Z pin and the GND pin on the underside of the ODrive board. ## AS5047/AS5048 Encoders -The AS5047/AS5048 encoders are Hall Effect/Magenetic sensors that can serve as rotary encoders for the ODrive. +The AS5047/AS5048 encoders are Hall Effect/Magnetic sensors that can serve as rotary encoders for the ODrive. The AS5047 has 3 independent output interfaces: SPI, ABI, and PWM. The AS5048 has 4 independent output interfaces: SPI, ABI, I2C, and PWM. diff --git a/docs/getting-started.md b/docs/getting-started.md index 7d208e09..3785ea96 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -235,16 +235,18 @@ Let's get motor 0 up and running. The procedure for motor 1 is exactly the same,
Help, something isn't working!
- Check the encoder wiring and that the encoder is firmly connected to the motor. Check the value of `hex(odrv0.axis0.error)` and then refer to the [error code documentation](troubleshooting.md#error-codes) for details. + Check the encoder wiring and that the encoder is firmly connected to the motor. Check the value of `dump_errors(odrv0)` and then refer to the [error code documentation](troubleshooting.md#error-codes) for details. - Once you understand the error and have fixed its cause, you may clear the error state with (`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 = 0`). + Once you understand the error and have fixed its cause, you may clear the error state with (`dump_errors(odrv0, True)` Enter) and retry.
-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. +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. If the motor begins to vibrate either immediately or after being disturbed you will need to [lower the controller gains](control.md). 3. Send the motor a new position setpoint. `odrv0.axis0.controller.pos_setpoint = 10000` Enter. The units are in encoder counts. +4. At this point you will probably want to [Properly tune](control.md) the motor controller in order to maximize system performance. ## Other control modes -The default control mode is unfiltered position control in the absolute encoder reference frame. You may wish to use a controlled trajectory instead. Or you may wish to control position in a circular frame to allow continous rotation forever without growing the numeric value of the setpoint too large. +The default control mode is unfiltered position control in the absolute encoder reference frame. You may wish to use a controlled trajectory instead. Or you may wish to control position in a circular frame to allow continuous rotation forever without growing the numeric value of the setpoint too large. + You may also wish to control velocity (directly or with a ramping filter). You can also directly control the current of the motor, which is proportional to torque. @@ -340,7 +342,8 @@ The watchdog is fed using the `axis.watchdog_feed()` method of each axis. ## What's next? You can now: -* See what other [commands and parameters](commands.md) are available, including setting tuning parameters for better performance. +* [Properly tune](control.md) the motor controller to unlock the full potential of the ODrive. +* See what other [commands and parameters](commands.md) are available, in order to better control the ODrive. * Control the ODrive from your own program or hook it up to an existing system through one of it's [interfaces](interfaces.md). * See how you can improve the behavior during the startup procedure, like [bypassing encoder calibration](encoders.md#encoder-with-index-signal). diff --git a/docs/hoverboard.md b/docs/hoverboard.md index 9641ae6d..730391fd 100644 --- a/docs/hoverboard.md +++ b/docs/hoverboard.md @@ -1,14 +1,15 @@ # Hoverboard motor and remote control setup guide By popular request here follows a step-by-step guide on how to setup the ODrive to drive hoverboard motors using RC PWM input. -Each step is acompanied by some explanation so hopefully you can carry over some of the steps to other setups and configurations. +Each step is accompanied by some explanation so hopefully you can carry over some of the steps to other setups and configurations. + [![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/ponx_U4xhoM/0.jpg)](https://www.youtube.com/watch?v=ponx_U4xhoM)
Click above to play video. ### Hoverboard motor wiring Hoverboard motors come with three motor phases (usually colored yellow, blue, green) which are thicker, and a set of 5 thinner wires for the hall sensor feedback (usually colored red, yellow, blue, green, black). -You may wire the motor phases in any order into a motor connector on the ODrive, as we will calibrate the phase alignment later anyway. Wire the hall feedback into the ODrive J4 conenctor (make sure that the motor channel number matches) as follows: +You may wire the motor phases in any order into a motor connector on the ODrive, as we will calibrate the phase alignment later anyway. Wire the hall feedback into the ODrive J4 connector (make sure that the motor channel number matches) as follows: | Hall wire | J4 signal | |-----------|-----------| @@ -54,7 +55,7 @@ odrv0.axis0.controller.config.vel_limit = 1000 odrv0.axis0.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL ``` -In the next step we are going to start powering the motor and so we want to make sure that some of the above settings that requrie a reboot are applied first. +In the next step we are going to start powering the motor and so we want to make sure that some of the above settings that require a reboot are applied first. ```txt odrv0.save_configuration() odrv0.reboot() @@ -77,7 +78,7 @@ Check to see that there is no error and that the phase resistance and inductance phase_resistance = 0.1793474406003952 (float) ``` -If all looks good then you can tell the ODrive that saving this calibration to presistent memory is OK: +If all looks good then you can tell the ODrive that saving this calibration to persistent memory is OK: ```txt odrv0.axis0.motor.config.pre_calibrated = True ``` diff --git a/docs/interfaces.md b/docs/interfaces.md index 627c54c2..e9a8781d 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -35,15 +35,15 @@ The ODrive can be controlled over various ports and protocols. If you're comfort Notes: * You must also connect GND between ODrive and your other board. * ODrive v3.3 and onward have 5V tolerant GPIO pins. -* ODrive v3.5 and later have some noise supression filters on the default step/dir pins +* ODrive v3.5 and later have some noise suppression filters on the default step/dir pins * You can change the step/dir pins using `axis.config._gpio_pin`. ### Pin function priorities 1. PWM in, if enabled. Disabled by default. 1. UART, **Enabled by default**. 1. Step/Dir, if enabled. Disabled by default. -1. Analog, default behaviour if not overriden (only on supported pins). -1. Digital in, default behaviour on pins not capable of analog input. +1. Analog, default behavior if not overridden (only on supported pins). +1. Digital in, default behavior on pins not capable of analog input. For predictable results, try to have only one feature enabled for any one pin. When changing pin assignments you must: * `odrv0.save_configuration()` @@ -90,7 +90,7 @@ This is a simpler alternative to the native protocol if you don't need all its b For more details, see the [ASCII protocol specification](ascii-protocol.md). ### Arduino -There is an Arduino library that gives some expamples on how to use the ASCII protocol to communicate with the ODrive. Check it out [here](../Arduino/ODriveArduino). +There is an Arduino library that gives some examples on how to use the ASCII protocol to communicate with the ODrive. Check it out [here](../Arduino/ODriveArduino). ## Step/direction This is the simplest possible way of controlling the ODrive. It is also the most primitive and fragile one. So don't use it unless you must interoperate with other hardware that you don't control. diff --git a/docs/odrivetool.md b/docs/odrivetool.md index 52b648bb..13616a37 100644 --- a/docs/odrivetool.md +++ b/docs/odrivetool.md @@ -198,7 +198,7 @@ If something doesn't work, make sure `openocd` is in your `PATH` variable, check ## Liveplotter -Liveplotter is used for the graphical plotting of odrive parameters (i.e. position) in real time. To start liveplotter, close any other instances of liveplotter and run `odrivetool liveplotter` from a new anaconda prompt window. By defult two parameters are plotted on startup; the encoder positon of axis 1 and axis 2. In the below example the motors are running in `closed_loop_control` while they are being forced off position by hand. +Liveplotter is used for the graphical plotting of odrive parameters (i.e. position) in real time. To start liveplotter, close any other instances of liveplotter and run `odrivetool liveplotter` from a new anaconda prompt window. By default two parameters are plotted on startup; the encoder position of axis 1 and axis 2. In the below example the motors are running in `closed_loop_control` while they are being forced off position by hand. ![Liveplotter position plot](figure_1.png) @@ -232,7 +232,7 @@ plot_rate = 10 num_samples = 1000 ``` -For more examples on how to interact with the plotting functinality refer to the [Matplotlib examples.](https://matplotlib.org/examples) +For more examples on how to interact with the plotting functionality refer to the [Matplotlib examples.](https://matplotlib.org/examples) ### Liveplotter from interactive odrivetool instance You can also run `start_liveplotter(...)` directly from the interactive odrivetool prompt. This is useful if you want to issue commands or otherwise keep interacting with the odrive while plotting. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 5b2ff466..2307c4bd 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -135,13 +135,13 @@ You can also try increasing `.controller.config.vel_limit_tolerance`. The ### Motor vibrates when stationary or makes constant noise -- Likely due to incorrect gains, specifically `vel_gain` may be set too high. Try following the [tuning procedure](https://docs.odriverobotics.com/commands). +- Likely due to incorrect gains, specifically `vel_gain` may be set too high. Try following the [tuning procedure](control.md#Tuning). - Check encoder shaft connection. Grub screws may vibrate lose with time. If using a CUI shaft encoder try remounting the plastic retaining ring and confirm that it is not coming into contact with the encoder housing. Also confirm that the encoder is securely mounted. - If you are using a high resolution encoder (>4000 counts/rotation) then increasing encoder_pll_bandwidth may help reduce vibration. - If you connect your motor to an object with a large moment of inertia (such as a flywheel) this will help reduce vibrations at high gians. However, make sure that all connections are ridged. Cheap shaft couplers or belts under low tension can introduce enough flex into a system that the motor may still vibrate independently. ### Motor overshoots target position or oscillates back and forth -- Likely due to incorrect gains for a given motor current limit. Specifically `pos_gain` is set too high. Try following the [tuning procedure](https://docs.odriverobotics.com/commands). +- Likely due to incorrect gains for a given motor current limit. Specifically `pos_gain` is set too high. Try following the [tuning procedure](control.md#Tuning). - Increase the current limit of your motor for more torque. ### Motor slowly starts to increase in speed From 98e1c19f55e6f0714ced16f87b4f7eb1aaed8234 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 27 Jul 2019 20:59:38 -0700 Subject: [PATCH 2/2] Update control.md --- docs/control.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/control.md b/docs/control.md index adc1ba6e..8af90a91 100644 --- a/docs/control.md +++ b/docs/control.md @@ -47,5 +47,4 @@ An upcoming feature will enable automatic tuning. Until then, here is a rough tu The liveplotter tool can be immensely helpful in dialing in these values. To display a graph that plots the position setpoint vs the measured position value run the following in the ODrive tool: -`start_liveplotter(lambda:[odrv0.axis0.encoder.pos_estimate - odrv0.axis0 - ...: .controller.pos_setpoint])` +`start_liveplotter(lambda:[odrv0.axis0.encoder.pos_estimate, odrv0.axis0.controller.pos_setpoint])`