diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 00000000..18f8786e --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,67 @@ +name: Build and publish HTML documentation website + +on: + push: + branches: [ feature/doc_autogen ] + +jobs: + jekyll: + runs-on: ubuntu-16.04 + steps: + - uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + + # Use GitHub Actions' cache for ruby and python packages to shorten build times and decrease load on servers + - name: Cache gems + uses: actions/cache@v2 + with: + path: docs/vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('docs/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-PyYAML-Jinja2-jsonschema + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + + - name: Install Python dependencies + run: pip install PyYAML Jinja2 jsonschema + + # Autogenerate the API reference .md files in the python in the python/python3 container + - name: Autogenerate the API reference .md files in the python container + run: | + mkdir -p docs/_api docs/_includes + python Firmware/interface_generator_stub.py --definitions Firmware/odrive-interface.yaml --template docs/_layouts/api_documentation_template.j2 --outputs docs/_api/#.md + python Firmware/interface_generator_stub.py --definitions Firmware/odrive-interface.yaml --template docs/_layouts/api_index_template.j2 --output docs/_includes/apiindex.html + + - name: Build the site in the jekyll/builder container + run: | + docker run \ + -v ${{ github.workspace }}:/srv/jekyll -e PAGES_REPO_NWO=${GITHUB_REPOSITORY} \ + ruby:2.7-buster /bin/sh -c " + chmod 777 /srv/jekyll/docs && \ + cd /srv/jekyll/docs && \ + bundle config path vendor/bundle && \ + bundle install && \ + JEKYLL_ENV=production bundle exec jekyll build + " + touch .nojekyll + + - name: Push to documentation branch + run: | + git config user.name "${GITHUB_ACTOR}" + git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" + git add -f docs/_site + git commit -m "jekyll build from Action ${GITHUB_SHA}" + git push --force origin HEAD:${REMOTE_BRANCH} + env: + REMOTE_BRANCH: gh-pages diff --git a/.gitignore b/.gitignore index 44828929..66a8990f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,9 +24,6 @@ coverage.xml # Django stuff: *.log -# Sphinx documentation -docs/_build/ - # PyBuilder target/ @@ -37,10 +34,18 @@ target/ .tup tup.config -docs/ruby-bundle -docs/_site -docs/.bundle -docs/Gemfile.lock +# Sphinx documentation +/docs/_build/ + +# Autogenerated API reference +/docs/_api +/docs/_includes/apiindex.html + +# Jekyll HTML documention and artifacts +/docs/ruby-bundle +/docs/_site +/docs/.bundle/config +/docs/.jekyll-metadata *.exe diff --git a/Firmware/fibre/tools/interface_generator.py b/Firmware/fibre/tools/interface_generator.py index cb8c6fa5..659d97f6 100644 --- a/Firmware/fibre/tools/interface_generator.py +++ b/Firmware/fibre/tools/interface_generator.py @@ -623,13 +623,17 @@ def tokenize(text, interface, interface_transform, value_type_transform, attribu token_list = split_name(token) # Check if this is an attribute reference - attr_intf = interface - for name in token_list: - if not name in attr_intf['attributes']: - attr = None - break - attr = attr_intf['attributes'][name] - attr_intf = attr['type'] + scope = interface + attr = None + while attr is None and not scope is None: + attr_intf = scope + for name in token_list: + if not name in attr_intf['attributes']: + attr = None + break + attr = attr_intf['attributes'][name] + attr_intf = attr['type'] + scope = scope.get('parent', None) if not attr is None: return attribute_transform(token, attr) @@ -648,6 +652,7 @@ env.filters['first'] = lambda x: next(iter(x)) env.filters['skip_first'] = lambda x: list(x)[1:] env.filters['to_c_string'] = lambda x: '\n'.join(('"' + line.replace('"', '\\"') + '"') for line in json.dumps(x, separators=(',', ':')).replace('{"name"', '\n{"name"').split('\n')) env.filters['tokenize'] = tokenize +env.filters['diagonalize'] = lambda lst: [lst[:i + 1] for i in range(len(lst))] template = env.from_string(template_file.read()) diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 204e51ad..eab23463 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -8,9 +8,26 @@ dictionary: [ODrive] # Prevent the word 'ODrive' from being detected as two word interfaces: ODrive: c_is_class: True + brief: Toplevel interface of your ODrive. + doc: | + The odrv0, odrv1, ... objects that appear in odrivetool implement this + toplevel interface. attributes: - vbus_voltage: readonly float32 - ibus: readonly float32 + vbus_voltage: + type: readonly float32 + unit: V + brief: Voltage on the DC bus as measured by the ODrive. + ibus: + type: readonly float32 + unit: A + brief: Current on the DC bus as calculated by the ODrive. + doc: | + A positive value means that the ODrive is consuming power from the power supply, + a negative value means that the ODrive is sourcing power to the power supply. + + This value is equal to the sum of the motor currents and the brake resistor currents. + The motor currents are measured, the brake resistor current is calculated based on + `config.brake_resistance`. serial_number: readonly uint64 hw_version_major: readonly uint8 hw_version_minor: readonly uint8 @@ -65,7 +82,8 @@ interfaces: doc: 'TODO: changing this currently requires a reboot - fix this' uart_baudrate: type: uint32 - doc: "Defines the baudrate used on the UART interface. + doc: | + Defines the baudrate used on the UART interface. Some baudrates will have a small timing error due to hardware limitations. Here's an (incomplete) list of baudrates for ODrive v3.x: @@ -85,62 +103,71 @@ interfaces: 1.792 MBps | 1.826 MBps | 1.9 1.8432 MBps | 1.826 MBps | 0.93 - For more information refer to Section 30.3.4 and Table 142 (the column with f_PCLK = 42 MHz) in the STM datasheet: - https://www.st.com/content/ccc/resource/technical/document/reference_manual/3d/6d/5a/66/b4/99/40/d4/DM00031020.pdf/files/DM00031020.pdf/jcr:content/translations/en.DM00031020.pdf" + For more information refer to Section 30.3.4 and Table 142 (the column with f_PCLK = 42 MHz) in the + [STM datasheet](https://www.st.com/content/ccc/resource/technical/document/reference_manual/3d/6d/5a/66/b4/99/40/d4/DM00031020.pdf/files/DM00031020.pdf/jcr:content/translations/en.DM00031020.pdf). enable_i2c_instead_of_can: type: bool - doc: 'Changing this requires a reboot' + doc: Changing this requires a reboot. enable_ascii_protocol_on_usb: bool max_regen_current: float32 brake_resistance: type: float32 unit: Ohm - doc: Value of the brake resistor connected to the ODrive. Set to 0 to disable. + brief: Value of the brake resistor connected to the ODrive. + doc: Set to 0 to disable. dc_bus_undervoltage_trip_level: type: float32 unit: V - doc: Minimum voltage below which the motor stops operating. + brief: Minimum voltage below which the motor stops operating. dc_bus_overvoltage_trip_level: type: float32 unit: V - doc: Maximum voltage above which the motor stops operating. + brief: Maximum voltage above which the motor stops operating. + doc: | This protects against cases in which the power supply fails to dissipate the brake power if the brake resistor is disabled. The default is 26V for the 24V board version and 52V for the 48V board version. enable_dc_bus_overvoltage_ramp: type: bool - doc: 'If enabled, if the measured DC voltage exceeds `dc_bus_overvoltage_ramp_start`, + status: experimental + brief: Enables the DC bus overvoltage ramp feature. + doc: | + If enabled, if the measured DC voltage exceeds `dc_bus_overvoltage_ramp_start`, the ODrive will sink more power than usual into the the brake resistor in an attempt to bring the voltage down again. The brake duty cycle is increased by the following amount: - vbus_voltage == dc_bus_overvoltage_ramp_start => brake_duty_cycle += 0% - vbus_voltage == dc_bus_overvoltage_ramp_end => brake_duty_cycle += 100% + + * `vbus_voltage` == `dc_bus_overvoltage_ramp_start` => brake_duty_cycle += 0% + * `vbus_voltage` == `dc_bus_overvoltage_ramp_end` => brake_duty_cycle += 100% Remarks: - This feature is active even when all motors are disarmed. - - This feature is disabled if `brake_resistance` is non-positive.' + - This feature is disabled if `brake_resistance` is non-positive. dc_bus_overvoltage_ramp_start: type: float32 - doc: See `enable_dc_bus_overvoltage_ramp`. - Do not set this lower than your usual vbus_voltage, + status: experimental + brief: See `enable_dc_bus_overvoltage_ramp`. + doc: Do not set this lower than your usual `vbus_voltage`, unless you like fried brake resistors. dc_bus_overvoltage_ramp_end: type: float32 - doc: See `enable_dc_bus_overvoltage_ramp`. - Must be larger than `dc_bus_overvoltage_ramp_start`, + status: experimental + brief: See `enable_dc_bus_overvoltage_ramp`. + doc: Must be larger than `dc_bus_overvoltage_ramp_start`, otherwise the ramp feature is disabled. dc_max_positive_current: type: float32 unit: A - doc: Max current the power supply can source. + brief: Max current the power supply can source. dc_max_negative_current: type: float32 unit: A - doc: Max current the power supply can sink. You most likely want a non-positive value here. Set to -INFINITY to disable. + brief: Max current the power supply can sink. + doc: You most likely want a non-positive value here. Set to -INFINITY to disable. gpio1_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[0]'} # TODO: disable for ODrive v3.2 and older gpio2_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[1]'} # TODO: disable for ODrive v3.2 and older @@ -192,9 +219,48 @@ interfaces: nullflag: 'None' flags: InvalidState: - doc: An invalid state was requested. + brief: An invalid state was requested. + doc: | + You tried to run a state before you are allowed to. Typically you + tried to run encoder calibration or closed loop control before the + motor was calibrated, or you tried to run closed loop control + before the encoder was calibrated. DcBusUnderVoltage: + brief: The DC voltage fell below the limit configured in `config.dc_bus_undervoltage_trip_level`. + doc: | + Confirm that your power leads are connected securely. For initial + testing a 12V PSU which can supply a couple of amps should be + sufficient while the use of low current ‘wall wart’ plug packs may + lead to inconsistent behaviour and is not recommended. + + You can monitor your PSU voltage using liveplotter in odrivetool + by entering `start_liveplotter(lambda: [odrv0.vbus_voltage])`. If + you see your votlage drop below `config.dc_bus_undervoltage_trip_level` + (default: ~ 8V) then you will trip this error. Even a relatively + small motor can draw multiple kW momentary and so unless you have + a very large PSU or are running of a battery you may encounter + this error when executing high speed movements with a high current + limit. To limit your PSU power draw you can limit your motor + current and/or velocity limit `controller.config.vel_limit` and + `motor.config.current_lim`. DcBusOverVoltage: + brief: The DC voltage exceeded the limit configured in `config.dc_bus_overvoltage_trip_level`. + doc: | + Confirm that you have a brake resistor of the correct value + connected securely and that `config.brake_resistance` is set to + the value of your brake resistor. + + You can monitor your PSU voltage using liveplotter in odrivetool + by entering `start_liveplotter(lambda: [odrv0.vbus_voltage])`. If + during a move you see the voltage rise above your PSU’s nominal + set voltage then you have your brake resistance set too low. This + may happen if you are using long wires or small gauge wires to + connect your brake resistor to your odrive which will added extra + resistance. This extra resistance needs to be accounted for to + prevent this voltage spike. If you have checked all your + connections you can also try increasing your brake resistance by + ~ 0.01 Ohm at a time to a maximum of 0.05 greater than your brake + resistor value. CurrentMeasurementTimeout: BrakeResistorDisarmed: doc: The brake resistor was unexpectedly disarmed. @@ -207,7 +273,7 @@ interfaces: doc: Check `encoder.error` for more information. ControllerFailed: PosCtrlDuringSensorless: - doc: DEPRECATED + status: deprecated WatchdogTimerExpired: MinEndstopPressed: MaxEndstopPressed: @@ -251,7 +317,7 @@ interfaces: enable_step_dir: type: bool doc: Enable step/dir input after calibration. - For M0 this has no effect if `enable_uart` is true. + For M0 this has no effect if `config.enable_uart` is true. step_dir_always_on: type: bool doc: Keep step/dir enabled while the motor is disabled. @@ -327,13 +393,71 @@ interfaces: nullflag: None flags: PhaseResistanceOutOfRange: + brief: The measured motor phase resistance is outside of the plausible range. + doc: | + During calibration the motor resistance and + [inductance](https://en.wikipedia.org/wiki/Inductance) is measured. + If the measured motor resistance or inductance falls outside a set + range this error will be returned. Check that all motor leads are + connected securely. + + The measured values can be viewed using odrivetool as is shown below: + ``` + In [2]: odrv0.axis0.motor.config.phase_inductance + Out[2]: 1.408751450071577e-05 + + In [3]: odrv0.axis0.motor.config.phase_resistance + Out[3]: 0.029788672924041748 + ``` + Some motors will have a considerably different phase resistance + and inductance than this. For example, gimbal motors, some small + motors (e.g. < 10A peak current). If you think this applies to you + try increasing `config.resistance_calib_max_voltage` from + its default value of 1 using odrivetool and repeat the motor + calibration process. If your motor has a small peak current draw + (e.g. < 20A) you can also try decreasing + `config.calibration_current` from its default value of 10A. + + In general, you need + ```text + resistance_calib_max_voltage > calibration_current * phase_resistance + resistance_calib_max_voltage < 0.5 * vbus_voltage + ``` PhaseInductanceOutOfRange: + brief: The measured motor phase inductance is outside of the plausible range. + doc: | + See `PhaseResistanceOutOfRange` for details. AdcFailed: DrvFault: + brief: The gate driver chip reported an error. + doc: | + The ODrive v3.4 is known to have a hardware issue whereby the + motors would stop operating when applying high currents to M0. The + reported error of both motors in this case is `ERROR_DRV_FAULT`. + + The conjecture is that the high switching current creates large + ripples in the power supply of the DRV8301 gate driver chips, thus + tripping its under-voltage fault detection. + + To resolve this issue you can limit the M0 current to 40A. The + lowest current at which the DRV fault was observed is 45A on one + test motor and 50A on another test motor. Refer to + [this post](https://discourse.odriverobotics.com/t/drv-fault-on-odrive-v3-4/558) + for instructions for a hardware fix. ControlDeadlineMissed: NotImplementedMotorType: BrakeCurrentOutOfRange: ModulationMagnitude: + doc: | + The bus voltage was insufficent to push the requested current + through the motor. + If you are getting this during motor calibration, make sure that + `config.resistance_calib_max_voltage` is no more than half + your bus voltage. + + For gimbal motors, it is recommended to set the + `config.calibration_current` and `config.current_lim` + to half your bus voltage, or less. BrakeDeadtimeViolation: UnexpectedTimerCallback: CurrentSenseSaturation: @@ -452,6 +576,18 @@ interfaces: nullflag: None flags: Overspeed: + doc: | + Try increasing `config.vel_limit`. The default of 20,000 encoder + counts per second gives a motor speed of only ~146 RPM with the + common CUI-AMT102 8192 count per rotation encoder. Note: Even if + you do not commanded your motor to exceed `config.vel_limit` + sudden changes in the load placed on a motor may cause this speed + to be temporarily exceeded, resulting in this error. + + You can also try increasing `config.vel_limit_tolerance`. The + default value of 1.2 means it will only allow a 20% violation of + the speed limit. You can set the `config.vel_limit_tolerance` to 0 + to disable the check altogether. InvalidInputMode: UnstableGain: InvalidMirrorAxis: @@ -525,7 +661,15 @@ interfaces: cogging_ratio: readonly float32 anticogging_enabled: bool functions: - move_incremental: {in: {displacement: float32, from_input_pos: bool}} + move_incremental: + doc: Moves the axes' goal point by a specified increment. + in: + displacement: {type: float32, doc: The desired position change.} + from_input_pos: {type: bool, doc: + 'If true, the increment is applied relative to `input_pos`. + If false, the increment is applied relative to `pos_setpoint`, which + usually corresponds roughly to the current position of the axis.' + } start_anticogging_calibration: @@ -537,10 +681,27 @@ interfaces: flags: UnstableGain: CprPolepairsMismatch: + doc: | + Confirm you have entered the correct count per rotation (CPR) for + [your encoder](https://docs.odriverobotics.com/encoders). The + ODrive uses your supplied value for the motor pole pairs to + measure the CPR. So you should also double check this value. + + Note that the AMT encoders are configurable using the micro- + switches on the encoder PCB and so you may need to check that + these are in the right positions. If your encoder lists its pulse + per rotation (PPR) multiply that number by four to get CPR. NoResponse: + doc: | + Confirm that your encoder is plugged into the right pins on the + ODrive board. UnsupportedEncoderMode: IllegalHallState: IndexNotFoundYet: + doc: | + Check that your encoder is a model that has an index pulse. If + your encoder does not have a wire connected to pin Z on your + ODrive then it does not output an index pulse. AbsSpiTimeout: AbsSpiComFail: AbsSpiNotReady: @@ -637,26 +798,54 @@ valuetypes: Undefined: doc: will fall through to idle Idle: - doc: disable PWM and do nothing + brief: Disable motor PWM and do nothing. StartupSequence: - doc: the actual sequence is defined by the config.startup... flags + brief: Run the startup procedure. + doc: the actual sequence is defined by the `config`.startup... flags FullCalibrationSequence: - doc: run all calibration procedures, then idle + doc: Run motor calibration and then encoder offset calibration (or encoder + index search if `.encoder.config.use_index` is `True`). MotorCalibration: - doc: run motor calibration + brief: Measure phase resistance and phase inductance of the motor. + doc: | + * To store the results set `motor.config.pre_calibrated` to `True` + and save the configuration (`save_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`. SensorlessControl: - doc: run sensorless control + brief: Run sensorless control. + doc: | + * The motor must be calibrated (`motor.is_calibrated`) + * `controller.config.control_mode` must be `True`. EncoderIndexSearch: - doc: run encoder index search + brief: Turn the motor in one direction until the encoder index is traversed. + doc: This state can only be entered if `encoder.config.use_index` is `True`. EncoderOffsetCalibration: - doc: run encoder offset calibration + brief: 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. + doc: | + * 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. ClosedLoopControl: - doc: run closed loop control + brief: Run closed loop control. + doc: | + * The action depends on the `controller.config.control_mode`. + * Can only be entered if the motor is calibrated + (`motor.is_calibrated`) and the encoder is ready (`encoder.is_ready`). LockinSpin: - doc: run lockin spin + brief: Run lockin spin. + doc: | + Can only be entered if the motor is calibrated (`motor.is_calibrated`) + or the motor direction is unspecified (`motor.config.direction` == 1) EncoderDirFind: + brief: Run encoder direction search. + doc: | + Can only be entered if the motor is calibrated (`motor.is_calibrated`). Homing: - doc: run axis homing function + brief: Run axis homing function. + doc: + Endstops must be enabled to use this feature. ODrive.Encoder.Mode: values: @@ -678,21 +867,112 @@ valuetypes: # Note: these should be sorted from lowest level of control to # highest level of control, to allow "<" style comparisons. VoltageControl: +<<<<<<< HEAD TorqueControl: +======= + doc: this one is not normally used + CurrentControl: +>>>>>>> 99507d2ece83127ca556f8fd34994075d517111e VelocityControl: PositionControl: ODrive.Controller.InputMode: values: Inactive: + brief: Disable inputs. Setpoints retain their last value. Passthrough: - VelRamp: - PosFilter: - MixChannels: - TrapTraj: - TorqueRamp: - Mirror: + brief: Pass `input_xxx` through to `xxx_setpoint` directly. + doc: | + ### Valid Inputs: + * `input_pos` + * `input_vel` + * `input_current` + ### Valid Control modes: + * `CONTROL_MODE_VOLTAGE_CONTROL` + * `CONTROL_MODE_CURRENT_CONTROL` + * `CONTROL_MODE_VELOCITY_CONTROL` + * `CONTROL_MODE_POSITION_CONTROL` + VelRamp: + brief: Ramps a velocity command from the current value to the target value. + doc: | + ### Configuration Values: + * `config.vel_ramp_rate` [cpr/sec] + * `config.inertia` [A/(count/s^2))] + + ### Valid inputs: + * `input_vel` + + ### Valid Control Modes: + * `CONTROL_MODE_VELOCITY_CONTROL` + PosFilter: + brief: Implements a 2nd order position tracking filter. + doc: | + Intended for use with step/dir interface, but can also be used with + position-only commands. + + ![POS Filter Response](../secondOrderResponse.PNG) + Result of a step command from 1000 to 0 + + ### Configuration Values: + * `config.input_filter_bandwidth` + * `config.inertia` + + ### Valid inputs: + * `input_pos` + + ### Valid Control modes: + * `CONTROL_MODE_POSITION_CONTROL` + MixChannels: + brief: Not Implemented. + TrapTraj: +<<<<<<< HEAD + TorqueRamp: +======= + brief: Implementes an online trapezoidal trajectory planner. + doc: | + ![Trapezoidal Planner Response](../TrapTrajPosVel.PNG) + + ### Configuration Values: + * `trap_traj.config.vel_limit` + * `trap_traj.config.accel_limit` + * `trap_traj.config.decel_limit` + * `config.inertia` + + ### Valid Inputs: + * `input_pos` + + ### Valid Control Modes: + * `CONTROL_MODE_POSITION_CONTROL` + CurrentRamp: + brief: Ramp a current command from the current value to the target value. + doc: | + ### Configuration Values: + * `config.current_ramp_rate` + + ### Valid Inputs: + * `input_current` + + ### Valid Control Modes: + * `CONTROL_MODE_CURRENT_CONTROL` +>>>>>>> 99507d2ece83127ca556f8fd34994075d517111e + Mirror: + brief: Implements "electronic mirroring". + doc: | + This is like electronic camming, but you can only mirror exactly the + movements of the other motor, according to a fixed ratio. + + [![](http://img.youtube.com/vi/D4_vBtyVVzM/0.jpg)](http://www.youtube.com/watch?v=D4_vBtyVVzM "Example Mirroring Video") + + ### Configuration Values + * `config.axis_to_mirror` + * `config.mirror_ratio` + + ### Valid Inputs + * None. Inputs are taken directly from the other axis encoder estimates + + ### Valid Control modes + * `CONTROL_MODE_POSITION_CONTROL` ODrive.Motor.MotorType: values: diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 93af7d5e..1490c10c 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,55 +1,57 @@ GEM remote: https://rubygems.org/ specs: - activesupport (4.2.9) - i18n (~> 0.7) + activesupport (6.0.3.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) + zeitwerk (~> 2.2, >= 2.2.2) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.11.1) colorator (1.1.0) - commonmarker (0.17.9) + commonmarker (0.17.13) ruby-enum (~> 0.5) - concurrent-ruby (1.0.5) + concurrent-ruby (1.1.6) + dnsruby (1.61.3) + addressable (~> 2.5) em-websocket (0.5.1) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) - ethon (0.11.0) + ethon (0.12.0) ffi (>= 1.3.0) - eventmachine (1.2.5) + eventmachine (1.2.7) execjs (2.7.0) - faraday (0.14.0) + faraday (1.0.1) multipart-post (>= 1.2, < 3) - ffi (1.9.24) + ffi (1.12.2) 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.4) - jekyll-avatar (= 0.5.0) + gemoji (3.0.1) + github-pages (206) + github-pages-health-check (= 1.16.1) + jekyll (= 3.8.7) + jekyll-avatar (= 0.7.0) jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.1.5) + jekyll-commonmark-ghpages (= 0.1.6) jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.9.3) + jekyll-feed (= 0.13.0) jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.9.4) - jekyll-mentions (= 1.3.0) - jekyll-optional-front-matter (= 0.3.0) + jekyll-github-metadata (= 2.13.0) + jekyll-mentions (= 1.5.1) + jekyll-optional-front-matter (= 0.3.2) 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-readme-index (= 0.3.0) + jekyll-redirect-from (= 0.15.0) + jekyll-relative-links (= 0.6.1) + jekyll-remote-theme (= 0.4.1) jekyll-sass-converter (= 1.5.2) - jekyll-seo-tag (= 2.4.0) - jekyll-sitemap (= 1.2.0) - jekyll-swiss (= 0.4.0) + jekyll-seo-tag (= 2.6.1) + jekyll-sitemap (= 1.4.0) + jekyll-swiss (= 1.0.0) jekyll-theme-architect (= 0.1.1) jekyll-theme-cayman (= 0.1.1) jekyll-theme-dinky (= 0.1.1) @@ -59,33 +61,32 @@ GEM 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-primer (= 0.5.4) 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) + jekyll-titles-from-headings (= 0.5.3) + jemoji (= 0.11.1) + kramdown (= 1.17.0) + liquid (= 4.0.3) mercenary (~> 0.3) - minima (= 2.4.0) - nokogiri (>= 1.8.5, < 2.0) - rouge (= 2.2.1) + minima (= 2.5.1) + nokogiri (>= 1.10.4, < 2.0) + rouge (= 3.19.0) terminal-table (~> 1.4) - github-pages-health-check (1.4.0) + github-pages-health-check (1.16.1) addressable (~> 2.3) - net-dns (~> 0.8) + dnsruby (~> 1.60) octokit (~> 4.0) - public_suffix (~> 2.0) + public_suffix (~> 3.0) typhoeus (~> 1.3) - html-pipeline (2.7.1) + html-pipeline (2.13.0) activesupport (>= 2) - nokogiri (>= 1.8.5) + nokogiri (>= 1.4) http_parser.rb (0.6.0) i18n (0.9.5) concurrent-ruby (~> 1.0) - jekyll (3.7.4) + jekyll (3.8.7) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) @@ -98,51 +99,50 @@ GEM pathutil (~> 0.9) rouge (>= 1.7, < 4) safe_yaml (~> 1.0) - jekyll-avatar (0.5.0) - jekyll (~> 3.0) + jekyll-avatar (0.7.0) + jekyll (>= 3.0, < 5.0) jekyll-coffeescript (1.1.1) coffee-script (~> 2.2) coffee-script-source (~> 1.11.1) - jekyll-commonmark (1.2.0) + jekyll-commonmark (1.3.1) commonmarker (~> 0.14) - jekyll (>= 3.0, < 4.0) - jekyll-commonmark-ghpages (0.1.5) + jekyll (>= 3.7, < 5.0) + jekyll-commonmark-ghpages (0.1.6) commonmarker (~> 0.17.6) - jekyll-commonmark (~> 1) - rouge (~> 2) + jekyll-commonmark (~> 1.2) + rouge (>= 2.0, < 4.0) jekyll-default-layout (0.1.4) jekyll (~> 3.0) - jekyll-feed (0.9.3) - jekyll (~> 3.3) + jekyll-feed (0.13.0) + jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) - jekyll-github-metadata (2.9.4) - jekyll (~> 3.1) + jekyll-github-metadata (2.13.0) + jekyll (>= 3.4, < 5.0) octokit (~> 4.0, != 4.4.0) - jekyll-mentions (1.3.0) - activesupport (~> 4.0) + jekyll-mentions (1.5.1) html-pipeline (~> 2.3) - jekyll (~> 3.0) - jekyll-optional-front-matter (0.3.0) - jekyll (~> 3.0) + jekyll (>= 3.7, < 5.0) + jekyll-optional-front-matter (0.3.2) + jekyll (>= 3.0, < 5.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.3.0, < 3.0) - typhoeus (>= 0.7, < 2.0) + jekyll-readme-index (0.3.0) + jekyll (>= 3.0, < 5.0) + jekyll-redirect-from (0.15.0) + jekyll (>= 3.3, < 5.0) + jekyll-relative-links (0.6.1) + jekyll (>= 3.3, < 5.0) + jekyll-remote-theme (0.4.1) + addressable (~> 2.0) + jekyll (>= 3.5, < 5.0) + rubyzip (>= 1.3.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-seo-tag (2.6.1) + jekyll (>= 3.3, < 5.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-swiss (1.0.0) jekyll-theme-architect (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) @@ -170,8 +170,8 @@ GEM jekyll-theme-modernist (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.5.3) - jekyll (~> 3.5) + jekyll-theme-primer (0.5.4) + jekyll (> 3.5, < 5.0) jekyll-github-metadata (~> 2.9) jekyll-seo-tag (~> 2.0) jekyll-theme-slate (0.1.1) @@ -183,62 +183,60 @@ GEM 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) + jekyll-titles-from-headings (0.5.3) + jekyll (>= 3.3, < 5.0) + jekyll-watch (2.2.1) listen (~> 3.0) - jemoji (0.9.0) - activesupport (~> 4.0, >= 4.2.9) + jemoji (0.11.1) 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) + jekyll (>= 3.0, < 5.0) + kramdown (1.17.0) + liquid (4.0.3) + listen (3.2.1) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) - mini_portile2 (2.3.0) - minima (2.4.0) - jekyll (~> 3.5) + mini_portile2 (2.4.0) + minima (2.5.1) + jekyll (>= 3.5, < 5.0) 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.5) - mini_portile2 (~> 2.3.0) - octokit (4.8.0) + minitest (5.14.1) + multipart-post (2.1.1) + nokogiri (1.10.9) + mini_portile2 (~> 2.4.0) + octokit (4.18.0) + faraday (>= 0.9) sawyer (~> 0.8.0, >= 0.5.3) - pathutil (0.16.1) + pathutil (0.16.2) 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) + public_suffix (3.1.1) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) + ffi (~> 1.0) + rouge (3.19.0) + ruby-enum (0.8.0) i18n - ruby_dep (1.5.0) - rubyzip (1.3.0) - safe_yaml (1.0.4) - sass (3.5.6) + rubyzip (2.3.0) + safe_yaml (1.0.5) + sass (3.7.4) 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) + sawyer (0.8.2) + addressable (>= 2.3.5) + faraday (> 0.8, < 2.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) - typhoeus (1.3.0) + typhoeus (1.4.0) ethon (>= 0.9.0) - tzinfo (1.2.5) + tzinfo (1.2.7) thread_safe (~> 0.1) - unicode-display_width (1.3.0) + unicode-display_width (1.7.0) + zeitwerk (2.3.0) PLATFORMS ruby @@ -248,4 +246,4 @@ DEPENDENCIES jekyll-redirect-from BUNDLED WITH - 1.16.1 + 2.1.4 diff --git a/docs/_config.yaml b/docs/_config.yaml index d753141e..54752e15 100644 --- a/docs/_config.yaml +++ b/docs/_config.yaml @@ -1,5 +1,8 @@ theme: jekyll-theme-minimal -exclude: [ruby-bundle] +exclude: [ruby-bundle, vendor] plugins: - jekyll-redirect-from google_analytics: UA-93396600-3 +collections: + api: + output: true diff --git a/docs/_data/index.yaml b/docs/_data/index.yaml index 6d34a8d2..d14d3c73 100644 --- a/docs/_data/index.yaml +++ b/docs/_data/index.yaml @@ -3,34 +3,37 @@ # https://jekyllrb.com/tutorials/navigation/#scenario-8-retrieving-items-based-on-front-matter-properties sections: - - title: For Users + - title: General docs: - title: Getting Started url: / - title: ODrive Tool - url: odrivetool + url: /odrivetool - title: Parameters & Commands - url: commands + url: /commands - title: Interfaces - url: interfaces + url: /interfaces - title: Encoders - url: encoders + url: /encoders - title: Homing & Endstops - url: endstops + url: /endstops - title: Control & Tuning - url: control - - title: Hoverboard Guide - url: hoverboard + url: /control - title: Troubleshooting - url: troubleshooting + url: /troubleshooting + - title: Tutorials + docs: + - title: Hoverboard Guide + url: /hoverboard + - title: API Reference - title: For ODrive Developers docs: - title: Firmware Developer Guide - url: developer-guide + url: /developer-guide - title: Configuring Visual Studio Code - url: configuring-vscode + url: /configuring-vscode - title: Configuring Eclipse - url: configuring-eclipse + url: /configuring-eclipse - title: Component Guides docs: - title: Motor Guide diff --git a/docs/_layouts/api_documentation_template.j2 b/docs/_layouts/api_documentation_template.j2 new file mode 100644 index 00000000..53fd127a --- /dev/null +++ b/docs/_layouts/api_documentation_template.j2 @@ -0,0 +1,148 @@ +--- +title: '[% if interface %][[interface.fullname]][% else %][[enum.fullname]][% endif %]' +layout: default +edit_url: 'Firmware/odrive-interface.yaml' +download: + url: 'Firmware/odrive-interface.yaml' + text: 'download as YAML' +--- + +[%- macro interface_ref(type) -%] +**[['[']][[type.name]][[']']]([[type.fullname | lower]])** +[%- endmacro %] + +[%- macro value_type_ref(type) -%] +[%- if type.builtin -%] +[[type.name]] +[%- else -%] +[['[']][[type.name]][[']']]([[type.fullname | lower]]) +[%- endif %] +[%- endmacro %] + +[% macro attr_ref(token, attr) -%] +**[['[']][[token]][[']']]([[attr.parent.fullname | lower]]#[[attr.name]])** +[%- endmacro %] + +[% if interface %] +[% set scope = interface %] +[% else %] +[% set scope = enum.parent %] +[% endif %] + +[%- macro doc_tokenize(text) %][[ text | tokenize(scope, interface_ref, value_type_ref, attr_ref) ]][% endmacro %] + +[%- macro status_badge(status) %] +[%- if status == 'experimental' %] +Experimental +[%- endif %] +[%- if status == 'deprecated' %] +Deprecated +[%- endif %] +[%- endmacro %] + +[%- macro breadcrumbs(title) %] +# [% for item in title.split('.') | diagonalize -%] +[[item[-1]]] +[%- if not loop.last %] 〉[% endif %] +[%- endfor %] +[%- endmacro %] + +[% if interface %] + +[[breadcrumbs(interface.fullname)]] + +[%- if interface.doc or interface.brief %] +[[doc_tokenize(interface.brief)]][% if interface.brief and interface.doc %] + +[% endif %][[doc_tokenize(interface.doc)]] +[%- endif %] + +## Attributes + +[% if interface.attributes %] +[% for attr in interface.attributes.values() %] +[%- if attr.type.purename == 'fibre.Property' %] +**[[attr.name]]**  —  [[value_type_ref(attr.type.value_type)]]    _[[attr.type.mode]]_ +[%- else %] +**[[attr.name]]**  —  [[interface_ref(attr.type)]] +[%- endif %] +[[-status_badge(attr.status)]] + + +[% endfor %] +[% else %] +This interface has no attributes. +[% endif %] + +## Functions + +[% if interface.functions %] +[% for function in interface.functions.values() %] +**[[function.name]]**([% for arg in function.in.values() | skip_first %][[arg.name]]: [[value_type_ref(arg.type)]][[', ' if not loop.last]][% endfor %])[% if function.out %]  ➔  [% for arg in function.out.values() %][[arg.name]]: [[value_type_ref(arg.type)]][[', ' if not loop.last]][% endfor %][% endif %] + + +[% endfor %] +[% else %] +This interface has no functions. +[% endif %] + +[% else %] + +[[breadcrumbs(enum.fullname)]] + +[%- if enum.doc or enum.brief %] +[[doc_tokenize(enum.brief)]][% if enum.brief and enum.doc %] + +[% endif %][[doc_tokenize(enum.doc)]] +[%- endif %] + +## [% if enum.is_flags %]Flags[% else %]Values[% endif %] + +[% for k, value in enum['values'].items() %] +**[[(enum.name + value.name) | to_macro_case]]**  —  [% if enum.is_flags %]0x[['%08x' | format(value.value)]][% else %][[value.value]][% endif %] +[[-status_badge(value.status)]] + + +[% endfor %] + +[% endif %] diff --git a/docs/_layouts/api_index_template.j2 b/docs/_layouts/api_index_template.j2 new file mode 100644 index 00000000..087fbced --- /dev/null +++ b/docs/_layouts/api_index_template.j2 @@ -0,0 +1,41 @@ +[%- macro dump_interfaces(interfaces, level) %] +[%- for intf in interfaces %] +[%- if intf.interfaces or intf.value_types %] +
  • +{% assign myvar = (page.title + '.') | split: "[[intf.fullname + '.']]" %} + + + +
  • +[%- else %] +
  • + +
  • +[%- endif %] +[%- endfor %] +[%- endmacro %] + +[%- macro dump_value_types(value_types, level) %] +[%- for enum in value_types %] +
  • + +
  • +[%- endfor %] +[%- endmacro %] + +[[dump_interfaces(toplevel_interfaces, 0)]] diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 9be4b36e..5569b3a5 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -1,3 +1,6 @@ +{% assign pagename = page.url | replace_first: '/', '' | replace: '.html', '' %} +{% if pagename == '' %}{% assign pagename = 'getting-started' %}{% endif %} + @@ -23,20 +26,29 @@ {% endif %}

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

    - + +
    - +
    +
    {% if site.github.is_project_page %}

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

    {% endif %} @@ -59,15 +71,30 @@
    -
    - {% assign filename = page.url | replace_first: '/', '' | replace: '.html', '.md' %} - {% if filename == '' %}{% assign filename = 'getting-started.md' %}{% endif %} - - - edit on GitHub -
    +
    +
    + + + {% if page.edit_url %} + {% assign edit_url = "https://www.github.com/madcowswe/ODrive/edit/master/" | append: edit_url %} + {% else %} + {% assign edit_url = "https://www.github.com/madcowswe/ODrive/edit/master/docs/" | append: pagename | append: ".md" %} + {% endif %} + edit on GitHub +
    + + {% if page.download %} +
    + + + + {{page.download.text}} +
    + {% endif %} +
    + {{ content }} @@ -105,6 +132,11 @@ } } + diff --git a/docs/assets/css/style.scss b/docs/assets/css/style.scss index 1ddd4fb8..92bf03ca 100644 --- a/docs/assets/css/style.scss +++ b/docs/assets/css/style.scss @@ -100,7 +100,7 @@ table { width:100%; border-collapse:collapse; display: block; - overflow-x: scroll; + overflow-x: auto; } th, td { @@ -128,10 +128,12 @@ header { float:left; position:fixed; -webkit-font-smoothing:subpixel-antialiased; - - overflow-y: auto; - top: 50px; + height: 100%; + display: flex; + flex-direction: column; + top: 0; bottom: 0; + padding: 10px 0; } @@ -261,7 +263,11 @@ a { } h1 a { - color: unset; + color: unset; +} + +.navitem a { + color: unset; } // a:hover, a:focus { @@ -270,6 +276,7 @@ h1 a { // } /*** Navigation bar ***/ + header > div { margin-right: 20px; } @@ -287,42 +294,68 @@ header li { #navbar { max-width: 250px; + flex: 1; + overflow: auto; + margin: 0; } -header ul p { - margin:0; +.navgroup { + background: #cbcbcb; + margin-top: 20px; +} +.navgroup:first-child { + margin-top: 0px; +} + +#navbar ul { + background-color: rgba(255, 255, 255, 0.87); + margin: 0; +} + +.navheader { + margin:0px; padding-left:5px; display: block; // color: #d60000; color: #000; font-weight: bold; - background-color: #cbcbcb; } -header ul ul li a { - background: #f8f8f8; +.navitem { //border:1px solid #e0e0e0; - line-height:1; font-size:12px; font-weight: bold; color:#676767; - display:block; + display:flex; text-align:left; - padding:12px 0px 5px 5px; - //margin:12px; - height:20px; + padding:0px 5px; + margin:0px; + height:37px; + line-height:37px; } -//// rounded edges (look bad) -//header ul p { -// border-radius:5px 5px 0 0; -//} -//header ul ul li:last-child a { -// border-radius:0 0 5px 5px; -//} +.navitem a { + display: block; + width: 100%; +} + +.levelbar { + float: inline-start; + margin-left: 8px; + margin-right: 5px; + border-left: 1px solid rgba(0, 0, 0, 0.3); + width: 1px !important; +} + +.currentitem { + //-webkit-box-shadow: inset 0px 0px 5px 3px #aa0000a6; + //-moz-box-shadow: inset 0px 0px 5px 3px #aa0000a6; + //box-shadow: inset 0px 0px 5px 3px #aa0000a6; + color: #d60000; +} /*** Navbar Hover ***/ -header ul a:hover, header ul a:focus { +.navitem:hover, .navitem:focus { color: #d60000; // color:rgb(0, 0, 0); // background-color: rgba(0, 0, 0, 0.24); @@ -418,11 +451,14 @@ details > div > p:last-child { border-left-color: #5bc0de; } -/*** edit link ***/ -.edit { +/*** edit/download link ***/ +.pageactions { float: right; font-size: 12px; } +.pageactions > div { + text-align: right; +} /*** inline code ***/ :not(pre) > code { @@ -441,3 +477,38 @@ table th { table tr:nth-child(2n) { background-color: #f8f8f8; } + + +.expandable-list { + height: 100%; + margin: 0px; + //background-color: #ffbfbf61; + max-height: 0; + overflow: hidden; + -webkit-transition: max-height .5s ease-in-out; + transition: max-height .5s ease-in-out; +} + +#navbar input[type=checkbox]:checked ~ .expandable-list { /* reset the height when checkbox is checked */ + max-height: 1000px; +} + +.chevron:before { + text-align: left; + content: "\3009" +} + +.chevron { + float: left; + -webkit-transition: -webkit-transform .5s ease; + transition: transform .5s ease; + transform-origin: 40% 50%; + padding-left: 5px; + padding-right: 5px; +} + +#navbar input[type=checkbox]:checked ~ p .chevron { /* rotate down when checkbox is checked */ + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} diff --git a/docs/commands.md b/docs/commands.md index c647adfe..15bf9cb3 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -18,30 +18,7 @@ For the most part, both axes on the ODrive can be controlled independently. ### State Machine -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. - 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.config.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`. - 5. `AXIS_STATE_SENSORLESS_CONTROL` Run sensorless control. - * The motor must be calibrated (`.motor.is_calibrated`) - * [`.controller.control_mode`](#control-mode) must be `True`. - 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. - 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`). - 9. `AXIS_STATE_LOCKIN_SPIN` Run lockin spin. - * Can only be entered if the motor is calibrated (`.motor.is_calibrated`) or the motor direction is unspecified (`.motor.config.direction == 1`) - 10. `AXIS_STATE_ENCODER_DIR_FIND` Run encoder direction search. - * Can only be entered if the motor is calibrated (`.motor.is_calibrated`). - 11. `AXIS_STATE_HOMING` Run axis homing function. - * Endstops must be enabled to use this feature. +The current state of an axis is indicated by [`.current_state`](api/odrive.axis#current_state). The user can request a new state by assigning a new value to [`.requested_state`](api/odrive.axis#current_state). The default state after startup is `AXIS_STATE_IDLE`. A description of all states can be found [here](api/odrive.axis.axisstate). ### Startup Procedure @@ -56,11 +33,12 @@ The ODrive will sequence all enabled startup actions selected in the order shown * `.config.startup_closed_loop_control` * `.config.startup_sensorless_control` -See [state machine](#state-machine) for a description of each state. +See [here](api/odrive.axis.axisstate) for a description of each state. ### Control Mode The default control mode is position control. If you want a different mode, you can change `.controller.config.control_mode`. +<<<<<<< HEAD Possible values are: * `CONTROL_MODE_POSITION_CONTROL` * `CONTROL_MODE_VELOCITY_CONTROL` @@ -83,10 +61,19 @@ Possible values are: For more information, see [input_modes](input_modes.md). # Control Commands +======= +Possible values are listed [here](api/odrive.axis.controller.controlmode). + +### Input Mode + +As of version v0.5.0, ODrive now intercepts the incoming commands and can apply filters to them. The old protocol values `pos_setpoint`, `vel_setpoint`, and `current_setpoint` are still used internally by the closed-loop cascade control, but the user cannot write to them directly. This allows us to condense the number of ways the ODrive accepts motion commands. The new commands are: + +>>>>>>> 99507d2ece83127ca556f8fd34994075d517111e * `.controller.input_pos = ` * `.controller.input_vel = ` * `.controller.input_torque = ` +<<<<<<< HEAD ### Input Mode To modify the way the control command affects the motor, you can use the input mode. The default input mode is pass through. If you want a different mode, you can change `.controller.config.input_mode`. @@ -99,6 +86,11 @@ Possible values are: * `INPUT_MODE_TRAP_TRAJ` * `INPUT_MODE_TORQUE_RAMP` * `INPUT_MODE_MIRROR` +======= +Modes can be selected by changing `.controller.config.input_mode`. +The default input mode is `INPUT_MODE_PASSTHROUGH`. +Possible values are listed [here](api/odrive.axis.controller.inputmode). +>>>>>>> 99507d2ece83127ca556f8fd34994075d517111e ## System monitoring commands diff --git a/docs/developer-guide.md b/docs/developer-guide.md index b169b1f7..f9fa7159 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -258,9 +258,14 @@ To run the docs server locally: ```bash cd docs -gem install bundler -bundle install --path ruby-bundle -bundle exec jekyll serve --host=0.0.0.0 +gem install bundler # The gem command typically comes with a Ruby installation +#export PATH="$PATH:~/.gem/ruby/2.7.0/bin" # or similar (depends on OS) +rm Gemfile.lock # only if below commands cause trouble +bundle config path ruby-bundle +bundle install +mkdir -p _api _includes +python ../Firmware/interface_generator_stub.py --definitions ../Firmware/odrive-interface.yaml --template _layouts/api_documentation_template.j2 --outputs _api/'#'.md && python ../Firmware/interface_generator_stub.py --definitions ../Firmware/odrive-interface.yaml --template _layouts/api_index_template.j2 --output _includes/apiindex.html +bundle exec jekyll serve --incremental --host=0.0.0.0 ``` ## Releases diff --git a/docs/input_modes.md b/docs/input_modes.md deleted file mode 100644 index 9a87bcee..00000000 --- a/docs/input_modes.md +++ /dev/null @@ -1,113 +0,0 @@ -# Input Modes -As of version ###, ODrive now intercepts the incoming commands and can apply filters to them. The old protocol values `pos_setpoint`, `vel_setpoint`, and `current_setpoint` are still used internally by the closed-loop cascade control, but the user cannot write to them directly. This allows us to condense the number of ways the ODrive accepts motion commands. The new commands are: - -* `.controller.config.input_mode` -* `.controller.input_pos` -* `.controller.input_vel` -* `.controller.input_torque` - -The Input Modes currently valid are: -* `INPUT_MODE_INACTIVE` -* `INPUT_MODE_PASSTHROUGH` -* `INPUT_MODE_VEL_RAMP` -* `INPUT_MODE_POS_FILTER` -* `INPUT_MODE_MIX_CHANNELS` -* `INPUT_MODE_TRAP_TRAJ` -* `INPUT_MODE_TORQUE_RAMP` -* `INPUT_MODE_MIRROR` - ---- - -## INPUT_MODE_INACTIVE -Disable inputs. Setpoints retain their last value. - -## INPUT_MODE_PASSTHROUGH -Pass `input_xxx` through to `xxx_setpoint` directly. - -### Valid Inputs: -* `input_pos` -* `input_vel` -* `input_torque` - -### Valid Control modes: -* `CONTROL_MODE_VOLTAGE_CONTROL` -* `CONTROL_MODE_TORQUE_CONTROL` -* `CONTROL_MODE_VELOCITY_CONTROL` -* `CONTROL_MODE_POSITION_CONTROL` - -## INPUT_MODE_VEL_RAMP -Ramps a velocity command from the current value to the target value. - -### Configuration Values: -* `.controller.config.vel_ramp_rate` [cpr/sec] -* `.controller.config.inertia` [A/(count/s^2))] - -### Valid inputs: -* `input_vel` - -### Valid Control Modes: -* `CONTROL_MODE_VELOCITY_CONTROL` - -## INPUT_MODE_POS_FILTER -Implements a 2nd order position tracking filter. Inteded for use with step/dir interface, but can also be used with position-only commands. - -![POS Filter Response](secondOrderResponse.png) -Result of a step command from 1000 to 0 - -### Configuration Values: -* `.controller.config.input_filter_bandwidth` -* `.controller.config.inertia` - -### Valid inputs: -* `input_pos` - -### Valid Control modes: -* `CONTROL_MODE_POSITION_CONTROL` - -## INPUT_MODE_MIX_CHANNELS -Not Implemented. - - -## INPUT_MODE_TRAP_TRAJ -Implementes an online trapezoidal trajectory planner. - -![Trapezoidal Planner Response](TrapTrajPosVel.png) - -### Configuration Values: -* `.trap_traj.config.vel_limit` -* `.trap_traj.config.accel_limit` -* `.trap_traj.config.decel_limit` -* `.controller.config.inertia` - -### Valid Inputs: -* `input_pos` - -### Valid Control Modes: -* `CONTROL_MODE_POSITION_CONTROL` - -## INPUT_MODE_TORQUE_RAMP -Ramp a torque command from the current value to the target value. - -### Configuration Values: -* `.controller.config.torque_ramp_rate` - -### Valid Inputs: -* `input_torque` - -### Valid Control Modes: -* `CONTROL_MODE_TORQUE_CONTROL` - -## INPUT_MODE_MIRROR -Implements "electronic mirroring". This is like electronic camming, but you can only mirror exactly the movements of the other motor, according to a fixed ratio - -[![](http://img.youtube.com/vi/D4_vBtyVVzM/0.jpg)](http://www.youtube.com/watch?v=D4_vBtyVVzM "Example Mirroring Video") - -### Configuration Values -* `.controller.config.axis_to_mirror` -* `.controller.config.mirror_ratio` - -### Valid Inputs -* None. Inputs are taken directly from the other axis encoder estimates - -### Valid Control modes -* `CONTROL_MODE_POSITION_CONTROL` diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 8419a8be..a06366eb 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -4,10 +4,6 @@ Table of Contents: - [Error codes](#error-codes) -- [Common Axis Errors](#common-axis-errors) -- [Common Motor Errors](#common-motor-errors) -- [Common Encoder Errors](#common-encoder-errors) -- [Common Controller Errors](#common-controller-errors) - [USB Connectivity Issues](#usb-connectivity-issues) - [Firmware Issues](#firmware-issues) - [Other issues that may not produce an error code](#other-issues-that-may-not-produce-an-error-code) @@ -17,94 +13,12 @@ Table of Contents: ## Error codes If your ODrive is not working as expected, run `odrivetool` and type `dump_errors(odrv0)` Enter. This will dump a list of all the errors that are present. To also clear all the errors, you can run `dump_errors(odrv0, True)`. -The following sections will give some guidance on the most common errors. You may also check the code for the full list of errors: -* Axis error flags defined [here](../Firmware/MotorControl/axis.hpp). -* Motor error flags defined [here](../Firmware/MotorControl/motor.hpp). -* Encoder error flags defined [here](../Firmware/MotorControl/encoder.hpp). -* Controller error flags defined [here](../Firmware/MotorControl/controller.hpp). -* Sensorless estimator error flags defined [here](../Firmware/MotorControl/sensorless_estimator.hpp). - -## Common Axis Errors - -* `ERROR_INVALID_STATE = 0x01` - -You tried to run a state before you are allowed to. Typically you tried to run encoder calibration or closed loop control before the motor was calibrated, or you tried to run closed loop control before the encoder was calibrated. - -* `ERROR_DC_BUS_UNDER_VOLTAGE = 0x02` - -Confirm that your power leads are connected securely. For initial testing a 12V PSU which can supply a couple of amps should be sufficient while the use of low current 'wall wart' plug packs may lead to inconsistent behaviour and is not recommended. - -You can monitor your PSU voltage using liveplotter in odrive tool by entering `start_liveplotter(lambda: [odrv0.vbus_voltage])`. If you see your votlage drop below ~ 8V then you will trip this error. Even a relatively small motor can draw multiple kW momentary and so unless you have a very large PSU or are running of a battery you may encounter this error when executing high speed movements with a high current limit. To limit your PSU power draw you can limit your motor current and/or velocity limit `odrv0.axis0.controller.config.vel_limit` and `odrv0.axis0.motor.config.current_lim`. - -* `ERROR_DC_BUS_OVER_VOLTAGE = 0x04` - -Confirm that you have a brake resistor of the correct value connected securly and that `odrv0.config.brake_resistance` is set to the value of your brake resistor. - -You can monitor your PSU voltage using liveplotter in odrive tool by entering `start_liveplotter(lambda: [odrv0.vbus_voltage])`. If during a move you see the voltage rise above your PSU's nominal set voltage then you have your brake resistance set too low. This may happen if you are using long wires or small gauge wires to connect your brake resistor to your odrive which will added extra resistance. This extra resistance needs to be accounted for to prevent this voltage spike. If you have checked all your connections you can also try increasing your brake resistance by ~ 0.01 Ohm at a time to a maximum of 0.05 greater than your brake resistor value. - -## Common Motor Errors - -* `ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x0001` and `ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x0002` - -During calibration the motor resistance and [inductance](https://en.wikipedia.org/wiki/Inductance) is measured. If the measured motor resistance or inductance falls outside a set range this error will be returned. Check that all motor leads are connected securely. - -The measured values can be viewed using odrivetool as is shown below: -``` -In [2]: odrv0.axis0.motor.config.phase_inductance -Out[2]: 1.408751450071577e-05 - -In [3]: odrv0.axis0.motor.config.phase_resistance -Out[3]: 0.029788672924041748 -``` -Some motors will have a considerably different phase resistance and inductance than this. For example, gimbal motors, some small motors (e.g. < 10A peak current). If you think this applies to you try increasing `odrv0.axis0.motor.config.resistance_calib_max_voltage` from its default value of 1 using odrivetool and repeat the motor calibration process. If your motor has a small peak current draw (e.g. < 20A) you can also try decreasing `odrv0.axis0.motor.config.calibration_current` from its default value of 10A. - -In general, you need -```text -resistance_calib_max_voltage > calibration_current * phase_resistance -resistance_calib_max_voltage < 0.5 * vbus_voltage -``` - -* `ERROR_DRV_FAULT = 0x0008` - -The ODrive v3.4 is known to have a hardware issue whereby the motors would stop operating -when applying high currents to M0. The reported error of both motors in this case -is `ERROR_DRV_FAULT`. - -The conjecture is that the high switching current creates large ripples in the -power supply of the DRV8301 gate driver chips, thus tripping its under-voltage fault detection. - -To resolve this issue you can limit the M0 current to 40A. The lowest current at which the DRV fault was observed is 45A on one test motor and 50A on another test motor. Refer to [this post](https://discourse.odriverobotics.com/t/drv-fault-on-odrive-v3-4/558) for instructions for a hardware fix. - -* `ERROR_MODULATION_MAGNITUDE = 0x0080` - -The bus voltage was insufficent to push the requested current through the motor. -If you are getting this during motor calibration, make sure that `motor.config.resistance_calib_max_voltage` is no more than half your bus voltage. - -For gimbal motors, it is recommended to set the `motor.config.calibration_current` and `motor.config.current_lim` to half your bus voltage, or less. - -## Common Encoder Errors - -* `ERROR_CPR_POLEPAIRS_MISMATCH = 0x02` - -Confirm you have entered the correct count per rotation (CPR) for [your encoder](https://docs.odriverobotics.com/encoders). The ODrive uses your supplied value for the motor pole pairs to measure the CPR. So you should also double check this value. - -Note that the AMT encoders are configurable using the micro-switches on the encoder PCB and so you may need to check that these are in the right positions. If your encoder lists its pulse per rotation (PPR) multiply that number by four to get CPR. - -* `ERROR_NO_RESPONSE = 0x04` - -Confirm that your encoder is plugged into the right pins on the odrive board. - -* `ERROR_INDEX_NOT_FOUND_YET = 0x20` - -Check that your encoder is a model that has an index pulse. If your encoder does not have a wire connected to pin Z on your odrive then it does not output an index pulse. - -## Common Controller Errors - -* `ERROR_OVERSPEED = 0x01` - -Try increasing `.controller.config.vel_limit`. The default `vel_limit` of 20,000 encoder counts per second gives a motor speed of only ~146 RPM with the common CUI-AMT102 8192 count per rotation encoder. Note: Even if you do not commanded your motor to exceed `vel_limit` sudden changes in the load placed on a motor may cause this speed to be temporarily exceeded, resulting in this error. - -You can also try increasing `.controller.config.vel_limit_tolerance`. The default value of 1.2 means it will only allow a 20% violation of the speed limit. You can set the `vel_limit_tolerance` to 0 to disable the check altogether. +With this information you can look up the API documentation for your error(s): +* Axis error flags documented [here](api/odrive.axis.error). +* Motor error flags documented [here](api/odrive.motor.error). +* Encoder error flags documented [here](api/odrive.encoder.error). +* Controller error flags documented [here](api/odrive.controller.error). +* Sensorless estimator error flags documented [here](odrive.sensorlessestimator.error). ## USB Connectivity Issues diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py index 3f19c7b2..88bbe3f0 100644 --- a/tools/odrive/tests/encoder_test.py +++ b/tools/odrive/tests/encoder_test.py @@ -121,7 +121,7 @@ class TestIncrementalEncoder(TestEncoderBase): def run_test(self, enc: ODriveEncoderComponent, teensy: TeensyComponent, teensy_gpio_a: TeensyGpio, teensy_gpio_b: TeensyGpio, logger: Logger): - true_cps = 8192*-0.5 # counts per second generated by the virtual encoder + true_cps = 8192*0.5 # counts per second generated by the virtual encoder code = teensy_incremental_encoder_emulation_code.replace("{enc_a}", str(teensy_gpio_a.num)).replace("{enc_b}", str(teensy_gpio_b.num)) teensy.compile_and_program(code) @@ -249,7 +249,7 @@ class TestHallEffectEncoder(TestEncoderBase): def run_test(self, enc: ODriveEncoderComponent, teensy: TeensyComponent, teensy_gpio_a: TeensyGpio, teensy_gpio_b: TeensyGpio, teensy_gpio_c: TeensyGpio, logger: Logger): true_cpr = 90 - true_rps = -1.0 + true_rps = 1.0 code = teensy_hall_effect_encoder_emulation_code.replace("{hall_a}", str(teensy_gpio_a.num)).replace("{hall_b}", str(teensy_gpio_b.num)).replace("{hall_c}", str(teensy_gpio_c.num)) teensy.compile_and_program(code) @@ -504,9 +504,9 @@ class TestSpiEncoder(TestEncoderBase): if __name__ == '__main__': test_runner.run([ - TestIncrementalEncoder(), - TestSinCosEncoder(), - TestHallEffectEncoder(), + #TestIncrementalEncoder(), + #TestSinCosEncoder(), + #TestHallEffectEncoder(), TestSpiEncoder(ENCODER_MODE_SPI_ABS_AMS), TestSpiEncoder(ENCODER_MODE_SPI_ABS_CUI), ]) diff --git a/tools/test-rig-rpi.yaml b/tools/test-rig-rpi.yaml index 4fb0b4e3..b9954049 100644 --- a/tools/test-rig-rpi.yaml +++ b/tools/test-rig-rpi.yaml @@ -78,11 +78,11 @@ connections: - ['teensy.gpio6', 'rpi.gpio20'] - ['teensy.gpio7', 'rpi.gpio19'] - ['teensy.gpio23', 'odrive.encoder0.z'] - - ['teensy.gpio22', 'odrive.encoder0.a'] - - ['teensy.gpio21', 'odrive.encoder0.b'] + - ['teensy.gpio22', 'odrive.encoder0.b'] + - ['teensy.gpio21', 'odrive.encoder0.a'] - ['teensy.gpio20', 'odrive.encoder1.z'] - - ['teensy.gpio19', 'odrive.encoder1.a'] - - ['teensy.gpio18', 'odrive.encoder1.b'] + - ['teensy.gpio19', 'odrive.encoder1.b'] + - ['teensy.gpio18', 'odrive.encoder1.a'] - ['teensy.gpio0', 'real_encoder.z'] - ['teensy.gpio1', 'real_encoder.a'] - ['teensy.gpio2', 'real_encoder.b']