porting docs from markdown to reStructuredText
364
docs/exts/fibre_autodoc.py
Normal file
@@ -0,0 +1,364 @@
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.nodes import Element, Node
|
||||
from docutils.parsers.rst import Directive, directives
|
||||
from docutils.statemachine import StringList
|
||||
import os
|
||||
import re
|
||||
from sphinx import addnodes
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.util.docutils import SphinxDirective, switch_source_input
|
||||
|
||||
# from sphinx.util.typing import OptionSpec # not present in windows/pip3 sphinx install
|
||||
from typing import Dict, Callable, Any, Tuple # needed to recreate OptionSpec
|
||||
OptionSpec = Dict[str, Callable[[str], Any]] # sphinx.util.typing.OptionSpec from GitHub
|
||||
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
sys.path.append(os.path.abspath('../tools/fibre-tools'))
|
||||
import interface_parser
|
||||
import type_registry
|
||||
|
||||
|
||||
def load_file(name, state):
|
||||
state.document.settings.record_dependencies.add(name)
|
||||
|
||||
def add_indent(lines: List[str], indent=' '):
|
||||
return [(indent + l) for l in lines]
|
||||
|
||||
class Documenter():
|
||||
pass
|
||||
|
||||
class MethodDocumenter(Documenter):
|
||||
objtype = 'method'
|
||||
|
||||
#def load_object(self, registry, name: str):
|
||||
# return registry.get_method(name)
|
||||
|
||||
@staticmethod
|
||||
def generate(registry, decl_ns_path, method):
|
||||
in_str = ', '.join(arg.name for arg in method.input_args)
|
||||
if len(method.output_args) == 0:
|
||||
out_str = ''
|
||||
elif len(method.output_args) == 1:
|
||||
out_str = ' -> ' + registry.get_py_val_type_name(decl_ns_path, method.output_args[0].type)
|
||||
else:
|
||||
out_str = ' -> tuple[' + ', '.join(registry.get_py_val_type_name(decl_ns_path, arg.type) for arg in method.output_args) + ']'
|
||||
|
||||
return [
|
||||
'',
|
||||
'.. py:method:: ' + method.name + '(' + in_str + ')' + out_str,
|
||||
*(['', ' ' + method.brief] if method.brief else []),
|
||||
*(['', ' ' + method.doc] if method.doc else []),
|
||||
'',
|
||||
*((' :param ' + registry.get_py_val_type_name(decl_ns_path, arg.type) + ' ' + arg.name + ':' + (' ' + arg.doc if arg.doc else '')) for arg in method.input_args),
|
||||
'',
|
||||
]
|
||||
|
||||
class AttributeDocumenter(Documenter):
|
||||
objtype = 'attribute'
|
||||
|
||||
@staticmethod
|
||||
def generate(registry, decl_ns_path, attr):
|
||||
return [
|
||||
'',
|
||||
'.. py:attribute:: ' + attr.name,
|
||||
' :type: ' + registry.get_py_ref_type_name(decl_ns_path, attr.type),
|
||||
*(['', ' ' + attr.brief] if attr.brief else []),
|
||||
*(['', ' ' + attr.doc] if attr.doc else []),
|
||||
''
|
||||
]
|
||||
|
||||
class EnumDocumenter(Documenter):
|
||||
objtype = 'enum'
|
||||
|
||||
@staticmethod
|
||||
def generate(registry, decl_ns_path, enum, options):
|
||||
lines = [
|
||||
'',
|
||||
'.. py:class:: ' + registry.get_py_val_type_name(decl_ns_path, enum),
|
||||
''
|
||||
]
|
||||
|
||||
for enumerator in enum.enumerators:
|
||||
lines += [
|
||||
'',
|
||||
' .. py:attribute:: ' + enumerator.name,
|
||||
' :value: {} (0x{:X})'.format(enumerator.value, enumerator.value),
|
||||
''
|
||||
]
|
||||
|
||||
return lines
|
||||
|
||||
class BitfieldDocumenter(Documenter):
|
||||
objtype = 'bitfield'
|
||||
|
||||
@staticmethod
|
||||
def generate(registry, decl_ns_path, bitfield, options):
|
||||
lines = [
|
||||
'',
|
||||
'.. py:class:: ' + registry.get_py_val_type_name(decl_ns_path, bitfield),
|
||||
''
|
||||
]
|
||||
|
||||
for flag in bitfield.flags:
|
||||
lines += [
|
||||
'',
|
||||
' .. py:attribute:: ' + flag.name,
|
||||
' :value: {} (0x{:X})'.format(1 << flag.bit, 1 << flag.bit),
|
||||
''
|
||||
]
|
||||
|
||||
return lines
|
||||
|
||||
class ClassDocumenter(Documenter):
|
||||
objtype = 'class'
|
||||
|
||||
@staticmethod
|
||||
def load_object(registry, name: str):
|
||||
cls = registry.get_class(name)
|
||||
return registry.get_containing_ns(cls), cls
|
||||
|
||||
@staticmethod
|
||||
def generate(registry, decl_ns_path, cls, options):
|
||||
lines = [
|
||||
'',
|
||||
'.. py:class:: ' + registry.get_py_ref_type_name(decl_ns_path, cls),
|
||||
''
|
||||
]
|
||||
|
||||
sub_decl_ns_path = registry.get_containing_ns(cls).get_path()[:2]
|
||||
|
||||
for method in cls.functions:
|
||||
lines += add_indent(MethodDocumenter().generate(registry, sub_decl_ns_path, method))
|
||||
|
||||
for attr in cls.attributes:
|
||||
lines += add_indent(AttributeDocumenter().generate(registry, sub_decl_ns_path, attr))
|
||||
|
||||
return lines
|
||||
|
||||
class NamespaceDocumenter(Documenter):
|
||||
objtype = 'namespace'
|
||||
|
||||
@staticmethod
|
||||
def load_object(registry, name: str):
|
||||
ns = registry.ns_from_name(name)
|
||||
return ns, ns
|
||||
|
||||
@staticmethod
|
||||
def generate(registry, decl_ns_path, ns, options):
|
||||
lines = []
|
||||
|
||||
for subtype in ns.types.values():
|
||||
if isinstance(subtype, interface_parser.EnumInfo) and ('enums' in options):
|
||||
lines += EnumDocumenter().generate(registry, decl_ns_path, subtype, options)
|
||||
elif isinstance(subtype, interface_parser.BitfieldInfo) and ('bitfields' in options):
|
||||
lines += BitfieldDocumenter().generate(registry, decl_ns_path, subtype, options)
|
||||
elif isinstance(subtype, interface_parser.ClassInfo) and ('classes' in options):
|
||||
lines += ClassDocumenter().generate(registry, decl_ns_path, subtype, options)
|
||||
else:
|
||||
raise Exception("Don't know how to document {} type".format(type(subtype)))
|
||||
|
||||
if 'namespaces' in options:
|
||||
for sub_ns in ns.namespaces.values():
|
||||
lines += NamespaceDocumenter().generate(registry, decl_ns_path, sub_ns, options)
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
|
||||
|
||||
documenter_list = [
|
||||
BitfieldDocumenter,
|
||||
EnumDocumenter,
|
||||
ClassDocumenter,
|
||||
NamespaceDocumenter,
|
||||
]
|
||||
documenters = {d.objtype: d for d in documenter_list}
|
||||
|
||||
|
||||
class FibredocDirective(SphinxDirective):
|
||||
"""
|
||||
Analogous to Sphinx autodoc class `AutodocDirective`.
|
||||
"""
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
option_spec: OptionSpec = {
|
||||
'bitfields': directives.flag,
|
||||
'enums': directives.flag,
|
||||
'classes': directives.flag,
|
||||
'namespaces': directives.flag,
|
||||
}
|
||||
|
||||
def run(self) -> List[Node]:
|
||||
# look up target Documenter
|
||||
objtype = self.name[5:] # strip prefix (fibre-).
|
||||
documenter = documenters[objtype]()
|
||||
|
||||
registry = self.env.app.fibre_registry
|
||||
|
||||
decl_ns, obj = documenter.load_object(registry, self.arguments[0])
|
||||
lines = documenter.generate(registry, decl_ns.get_path()[:2], obj, self.options)
|
||||
|
||||
result_rest = StringList()
|
||||
for line in lines:
|
||||
result_rest.append(line, 'fibre autogen output', 0)
|
||||
|
||||
#print("reST output: ", result_rest)
|
||||
|
||||
# Parse nested reST
|
||||
with switch_source_input(self.state, result_rest):
|
||||
node = nodes.paragraph()
|
||||
node.document = self.state.document
|
||||
self.state.nested_parse(result_rest, 0, node)
|
||||
return node.children
|
||||
|
||||
class fibresummary_toc(nodes.comment):
|
||||
pass
|
||||
|
||||
def autosummary_toc_visit_html(self: nodes.NodeVisitor, node: fibresummary_toc) -> None:
|
||||
"""Hide autosummary toctree list in HTML output."""
|
||||
raise nodes.SkipNode
|
||||
|
||||
def autosummary_noop(self: nodes.NodeVisitor, node: Node) -> None:
|
||||
pass
|
||||
|
||||
class FibresummaryDirective(SphinxDirective):
|
||||
required_arguments = 1
|
||||
optional_arguments = 0
|
||||
option_spec: OptionSpec = {
|
||||
'caption': directives.unchanged_required,
|
||||
}
|
||||
|
||||
def run(self) -> List[Node]:
|
||||
nodes = [] # TODO: generate table
|
||||
|
||||
docnames = ['fibre_types/' + self.arguments[0].replace('.', '_')]
|
||||
|
||||
tocnode = addnodes.toctree()
|
||||
tocnode['includefiles'] = docnames
|
||||
tocnode['entries'] = [(None, docn) for docn in docnames]
|
||||
tocnode['maxdepth'] = -1
|
||||
tocnode['glob'] = None
|
||||
tocnode['caption'] = self.options.get('caption')
|
||||
|
||||
nodes.append(fibresummary_toc('', '', tocnode))
|
||||
return nodes
|
||||
|
||||
def load_yaml_files(app, config):
|
||||
registry = type_registry.TypeRegistry()
|
||||
loader = interface_parser.Loader(registry)
|
||||
|
||||
for file in config.fibre_interface_files:
|
||||
loader.load_from_yaml_file(file)
|
||||
|
||||
registry.resolve_all()
|
||||
|
||||
app.fibre_registry = registry
|
||||
|
||||
def generate_stub_file(app: Sphinx, ns_path: Tuple[str], filename: str, deep: bool):
|
||||
title = '.'.join(ns_path[2:]) + " Reference"
|
||||
|
||||
lines = [
|
||||
title,
|
||||
"=" * len(title),
|
||||
]
|
||||
|
||||
parent_ns = app.fibre_registry.global_namespace.ns_from_path(ns_path[:-1], construct_if_missing=False)
|
||||
if not parent_ns is None:
|
||||
type = parent_ns.get_type(ns_path[-1], kind=None, construct_if_missing=False)
|
||||
if not type is None:
|
||||
lines.extend([
|
||||
"",
|
||||
".. fibreclass:: " + '.'.join(ns_path),
|
||||
"",
|
||||
])
|
||||
|
||||
#import ipdb; ipdb.set_trace()
|
||||
ns = app.fibre_registry.global_namespace.ns_from_path(ns_path, construct_if_missing=False)
|
||||
if not ns is None:
|
||||
lines.extend([
|
||||
"",
|
||||
".. fibrenamespace:: " + '.'.join(ns_path),
|
||||
" :bitfields:",
|
||||
" :enums:",
|
||||
" :classes:",
|
||||
" :namespaces:",
|
||||
"",
|
||||
])
|
||||
|
||||
|
||||
content = '\n'.join(lines)
|
||||
|
||||
if os.path.isfile(filename):
|
||||
with open(filename) as fp:
|
||||
old_content = fp.read()
|
||||
if content == old_content:
|
||||
return False
|
||||
|
||||
with open(filename, 'w') as fp:
|
||||
fp.write(content)
|
||||
return True
|
||||
|
||||
|
||||
def find_autosummary_in_lines(lines, filename):
|
||||
"""
|
||||
Inspired by find_autosummary_in_lines in the autosummary extension
|
||||
"""
|
||||
autosummary_re = re.compile(r'^(\s*)\.\.\s+fibreautosummary::\s*([A-Za-z0-9_.:]+)\s*$')
|
||||
|
||||
documented = []
|
||||
for line in lines:
|
||||
m = autosummary_re.match(line)
|
||||
if m:
|
||||
indent, name = m.groups()
|
||||
path = os.path.join(os.path.dirname(filename), 'fibre_types')
|
||||
documented.append((path, tuple(name.split('.'))))
|
||||
|
||||
return documented
|
||||
|
||||
def generate_stub_files(app: Sphinx):
|
||||
"""
|
||||
Inspired by process_generate_options in the autosummary extension.
|
||||
"""
|
||||
env = app.builder.env
|
||||
genfiles = [env.doc2path(x, base=None) for x in env.found_docs
|
||||
if os.path.isfile(env.doc2path(x))]
|
||||
|
||||
# read
|
||||
documented = []
|
||||
for filename in genfiles:
|
||||
with open(filename, encoding='utf-8', errors='ignore') as f:
|
||||
lines = f.read().splitlines()
|
||||
documented.extend(find_autosummary_in_lines(lines, filename=filename))
|
||||
|
||||
# write
|
||||
for out_dir, ns_name in documented:
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
out_file = os.path.join(out_dir, '_'.join(ns_name) + '.rst')
|
||||
generate_stub_file(app, ns_name, out_file, True)
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_node(fibresummary_toc,
|
||||
html=(autosummary_toc_visit_html, autosummary_noop),
|
||||
latex=(autosummary_noop, autosummary_noop),
|
||||
text=(autosummary_noop, autosummary_noop),
|
||||
man=(autosummary_noop, autosummary_noop),
|
||||
texinfo=(autosummary_noop, autosummary_noop))
|
||||
|
||||
app.add_config_value('fibre_interface_files', [], 'html')
|
||||
|
||||
for d in documenter_list:
|
||||
app.add_directive('fibre' + d.objtype, FibredocDirective)
|
||||
app.add_directive('fibreautosummary', FibresummaryDirective)
|
||||
|
||||
app.connect('config-inited', load_yaml_files)
|
||||
app.connect('builder-inited', generate_stub_files)
|
||||
|
||||
return {
|
||||
'version': '0.1',
|
||||
'parallel_read_safe': False, # global state: loaded interfaces
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
20
docs/reStructuredText/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
12
docs/reStructuredText/analog-input.rst
Normal file
@@ -0,0 +1,12 @@
|
||||
================================================================================
|
||||
Analog Input
|
||||
================================================================================
|
||||
|
||||
Analog inputs can be used to measure voltages between 0 and 3.3V.
|
||||
ODrive uses a 12 bit ADC (4096 steps) and so has a maximum resolution of 0.8 mV.
|
||||
A GPIO must be configured with :code:`<odrv>.config.gpioX_mode = GPIO_MODE_ANALOG_IN` before it can be used as an analog input.
|
||||
To read the voltage on GPIO1 in odrivetool the following would be entered: :code:`odrv0.get_adc_voltage(1)`.
|
||||
|
||||
Similar to RC PWM input, analog inputs can also be used to feed any of the numerical properties that are visible in :code:`odrivetool`.
|
||||
This is done by configuring :code:`odrv0.config.gpio3_analog_mapping` and :code:`odrv0.config.gpio4_analog_mapping`.
|
||||
Refer to :ref:`RC PWM <rc-pwm-doc>` for instructions on how to configure the mappings.
|
||||
180
docs/reStructuredText/ascii-protocol.rst
Normal file
@@ -0,0 +1,180 @@
|
||||
.. _ascii-protocol:
|
||||
|
||||
================================================================================
|
||||
ASCII Protocol
|
||||
================================================================================
|
||||
|
||||
.. contents::
|
||||
:depth: 1
|
||||
:local:
|
||||
|
||||
Sending Commands
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
* **Via USB:**
|
||||
* **Windows:** Use `PuTTY <https://www.chiark.greenend.org.uk/~sgtatham/putty/>`_ to manually send commands or open the COM port using your favorite programming language
|
||||
* **Linux/macOS:** Run :code:`/dev/tty*` to list all serial ports. The ODrive will show up as :code:`/dev/ttyACM0` (or similar) on Linux and :code:`/dev/tty.usbmodem[...]` on macOS.
|
||||
Once you know the name, you can use :code:`screen /dev/ttyACM0` (with the correct name) to send commands manually or open the device using your favorite programming language.
|
||||
Serial ports on Unix can be opened, written to and read from like a normal file.
|
||||
* **Via UART:** Connect the ODrive's TX (GPIO1) to your host's RX. Connect your ODrive's RX (GPIO2) to your host's TX. See :ref:`UART <uart-doc>` for more info.
|
||||
* **Arduino:** You can use the `ODrive Arduino library <https://github.com/madcowswe/ODrive/tree/master/Arduino/ODriveArduino>`_ to talk to the ODrive.
|
||||
* **Windows/Linux/macOS:** You can use an FTDI USB-UART cable to connect to the ODrive.
|
||||
|
||||
The ODrive does not echo commands. That means that when you type commands into a program like :code:`screen`, the characters you type won't show up in the console.
|
||||
|
||||
.. admonition:: Arduino
|
||||
|
||||
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>`.
|
||||
|
||||
Command Format
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
The ASCII protocol is human-readable and line-oriented, with each line having the following format:
|
||||
|
||||
Format: :code:`command *42 ; comment [new line character]`
|
||||
|
||||
* :code:`*42` stands for a GCode compatible checksum and can be omitted. If and only if a checksum is provided, the device will also include a checksum in the response, if any.
|
||||
If the checksum is provided but is not valid, the line is ignored. The checksum is calculated as the bitwise xor of all characters before the asterisk (`*`).
|
||||
Example of a valid checksum: :code:`r vbus_voltage *93`.
|
||||
* comments are supported for GCode compatibility
|
||||
* the command is interpreted once the new-line character is encountered
|
||||
|
||||
|
||||
.. _acsii-cmd-ref:
|
||||
|
||||
Command Reference
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
.. _motor_traj-cmd:
|
||||
|
||||
Motor Trajectory
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Format: :code:`t motor destination`
|
||||
|
||||
* :code:`t` for trajectory.
|
||||
* :code:`motor` is the motor number, :code:`0` or :code:`1`.
|
||||
* :code:`destination` is the goal position, in [turns].
|
||||
|
||||
Example::
|
||||
|
||||
t 0 -2
|
||||
|
||||
For general moving around of the axis, this is the recommended command.
|
||||
|
||||
This command updates the watchdog timer for the motor.
|
||||
|
||||
Motor Position
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For basic use where you send one setpoint at at a time, use the :code:`q` command.
|
||||
|
||||
Format: :code:`q motor position velocity_lim torque_lim`
|
||||
|
||||
* :code:`q` for position.
|
||||
* :code:`motor` is the motor number, :code:`0` or :code:`1`.
|
||||
* :code:`position` is the desired position, in [turns].
|
||||
* :code:`velocity_lim` is the velocity limit, in [turns/s] (optional).
|
||||
* :code:`torque_lim` is the torque limit, in [Nm] (optional).
|
||||
|
||||
Example::
|
||||
|
||||
q 0 -2 1 0.1
|
||||
|
||||
If you have a realtime controller that is streaming setpoints and tracking a trajectory, use the :code:`p` command.
|
||||
|
||||
Format: :code:`p motor position velocity_ff torque_ff`
|
||||
|
||||
* :code:`p` for position
|
||||
* :code:`motor` is the motor number, :code:`0` or :code:`1`.
|
||||
* :code:`position` is the desired position, in [turns].
|
||||
* :code:`velocity_ff` is the velocity feed-forward term, in [turns/s] (optional).
|
||||
* :code:`torque_ff` is the torque feed-forward term, in [Nm] (optional).
|
||||
|
||||
Example::
|
||||
|
||||
p 0 -2 0 0
|
||||
|
||||
This command updates the watchdog timer for the motor.
|
||||
|
||||
.. note:: If you don't know what feed-forward is or what it's used for, simply omit it.
|
||||
|
||||
Motor Velocity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Format: :code:`v motor velocity torque_ff`
|
||||
|
||||
* :code:`v` for velocity
|
||||
* :code:`motor` is the motor number, :code:`0` or :code:`1`.
|
||||
* :code:`velocity` is the desired velocity in [turns/s].
|
||||
* :code:`torque_ff` is the torque feed-forward term, in [Nm] (optional).
|
||||
|
||||
Example::
|
||||
|
||||
v 0 1 0
|
||||
|
||||
This command updates the watchdog timer for the motor.
|
||||
|
||||
|
||||
Motor Current
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Format: :code:`c motor torque`
|
||||
|
||||
* :code:`c` for torque
|
||||
* :code:`motor` is the motor number, :code:`0` or :code:`1`.
|
||||
* :code:`torque` is the desired torque in [Nm].
|
||||
|
||||
This command updates the watchdog timer for the motor.
|
||||
|
||||
|
||||
Request Feedback
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
imput format: :code:`f motor`
|
||||
|
||||
response format: :code:`pos vel`
|
||||
|
||||
* :code:`f` for feedback.
|
||||
* :code:`pos` is the encoder position in [turns] (float).
|
||||
* :code:`vel` is the encoder velocity in [turns/s] (float).
|
||||
|
||||
Update Motor Watchdog
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Format: :code:`u motor`
|
||||
|
||||
* :code:`u` for /u/pdate.
|
||||
|
||||
* :code:`motor` is the motor number, :code:`0` or :code:`1`.
|
||||
|
||||
This command updates the watchdog timer for the motor, without changing any
|
||||
setpoints.
|
||||
|
||||
Parameter Reading/Writing
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Not all parameters can be accessed via the ASCII protocol but at least all parameters with float and integer type are supported.
|
||||
|
||||
* Reading format: :code:`r [property]`
|
||||
|
||||
* :code:`property` name of the property, as seen in ODrive Tool
|
||||
* response: text representation of the requested value
|
||||
* Example: :code:`r vbus_voltage` => response: :code:`24.087744`
|
||||
|
||||
* Writing format: :code:`w [property] [value]`
|
||||
|
||||
* :code:`property` name of the property, as seen in ODrive Tool
|
||||
* :code:`value` text representation of the value to be written
|
||||
* Example::
|
||||
|
||||
w axis0.controller.input_pos -123.456
|
||||
|
||||
System Commands
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
* :code:`ss` - Save config
|
||||
* :code:`se` - Erase config
|
||||
* :code:`sr` - Reboot
|
||||
* :code:`sc` - Clear errors
|
||||
206
docs/reStructuredText/can-guide.rst
Normal file
@@ -0,0 +1,206 @@
|
||||
.. _can-guide:
|
||||
|
||||
================================================================================
|
||||
CAN Bus Guide for ODrive
|
||||
================================================================================
|
||||
|
||||
ODrive v3 supports CAN 2.0b. We've built a :ref:`simple protocol <can-protocol>` (named CANSimple) so that most ODrive functions can be controlled without a full CAN Open or similar stack.
|
||||
This guide is intended for beginners to set up CAN on the ODrive and on their host device.
|
||||
We will be focusing on Raspberry Pi and Arduino-compatible devices using the MCP2515 CAN Controller.
|
||||
|
||||
What is CAN bus?
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Borrowing from `Wikipeda <https://en.wikipedia.org/wiki/CAN_bus>`_:
|
||||
|
||||
> A Controller Area Network (CAN bus) is a robust vehicle bus standard designed to allow microcontrollers and devices to communicate with each other's applications without a host computer.
|
||||
It is a message-based protocol, designed originally for multiplex electrical wiring within automobiles to save on copper, but it can also be used in many other contexts.
|
||||
For each device, the data in a frame is transmitted sequentially but in such a way that if more than one device transmits at the same time, the highest priority device can continue while the others back off.
|
||||
Frames are received by all devices, including by the transmitting device.
|
||||
|
||||
In simple terms, CAN is a way of communicating between many devices over a single twisted pair of wires.
|
||||
The signal is transmitted as the difference in voltage between the two wires (differential signalling), which makes it very robust against noise.
|
||||
Instead of using a unique address (like I2C) or a select pin (like SPI), CAN *messages* have a unique ID that also acts as the priority.
|
||||
At the beginning of a message frame, all devices talk and read at the same time. As the message ID is transmitted, the lowest value "wins" and that message will be transmitted (ID **0** has the *highest* priority).
|
||||
All other devices will wait for the next chance to send. If two devices send the same message ID at the same time, they will conflict and a bus failure may occur.
|
||||
Make sure your devices can never send the same message ID at the same time!
|
||||
|
||||
See also `this great article <https://danfosseditron.zendesk.com/hc/en-gb/articles/360042232992-CAN-bus-physical-layer>`_ from Danfoss that quickly describes how to put together the wiring for a CAN bus
|
||||
|
||||
.. image:: figures/CAN_Bus_Drawing.png
|
||||
:scale: 60 %
|
||||
:align: center
|
||||
:alt: CAN picture
|
||||
|
||||
|
||||
|
||||
Why use CAN?
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
CAN is convenient for its simple and robust Physical Layer (PHY) that requires only a twisted pair of wires and a 120ohm termination resistor at each end.
|
||||
It has low jitter and low latency, because there is no host computer. It is relatively fast (CAN 2.0b supports 1 Mbps). Messages are easy to configure and load with data.
|
||||
Transceivers and controllers are inexpensive and widely available, thanks to its use in automotive.
|
||||
|
||||
Hardware Setup
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
ODrive assumes the CAN PHY is a standard differential twisted pair in a linear bus configuration with 120 ohm termination resistance at each end.
|
||||
ODrive versions less than V3.5 include a soldered 120 ohm termination resistor, but ODrive versions V3.5 and greater implement a dip switch to toggle the termination.
|
||||
ODrive uses 3.3v as the high output, but conforms to the CAN PHY requirement of achieving a differential voltage > 1.5V to represent a "0".
|
||||
As such, it is compatible with standard 5V bus architectures.
|
||||
|
||||
Setting up CAN on ODrive
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
CANSimple breaks the CAN Message ID into two parts: An axis ID and a command ID. By default, CAN is enabled on the ODrive, where Axis 0 has ID 0, and Axis 1 has ID 1.
|
||||
The ID of each axis should be unique; each should be set via :code:`odrivetool` before connecting to the bus with the command:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.<axis>.config.can.node_id = <number>
|
||||
|
||||
By default, ODrive supports a value up to 63 (:code:`0x3F`). See :ref:`can-protocol <can-protocol>` for more information.
|
||||
|
||||
You should also set the CAN bus speed on ODrive with the command
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.can.config.baud_rate = <number>
|
||||
|
||||
.. list-table::
|
||||
:widths: 25 25
|
||||
:header-rows: 1
|
||||
|
||||
* - Speed
|
||||
- value
|
||||
* - 125 kbps
|
||||
- 125000
|
||||
* - 250 kbps
|
||||
- 250000
|
||||
* - 500 kbps
|
||||
- 500000
|
||||
* - 1000 kbps
|
||||
- 1000000
|
||||
|
||||
That's it! You're ready to set up your host device.
|
||||
|
||||
Example
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.config.can.node_id = 0
|
||||
odrv0.axis1.config.can.node_id = 1
|
||||
odrv0.can.config.baud_rate = 250000
|
||||
|
||||
|
||||
Setting up a Raspberry Pi for CAN Communications
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
First, you will need a CAN Hat for your Raspberry Pi.
|
||||
We are using `this CAN hat <https://www.amazon.com/Raspberry-Long-Distance-Communication-Transceiver-SN65HVD230/dp/B07DQPYFYV>`_.
|
||||
|
||||
Setting up the Raspberry Pi essentially involves the following:
|
||||
#. Enable SPI communications to the MCP2515
|
||||
#. Install :code:`can-utils` with :code:`apt-get install can-utils`
|
||||
#. Creating a connection between your application and the :code:`can0` socket
|
||||
|
||||
There are many tutorials for this process.
|
||||
`This one is pretty good <https://www.hackster.io/youness/how-to-connect-raspberry-pi-to-can-bus-b60235>`_, and `this recent forum post <https://www.raspberrypi.org/forums/viewtopic.php?t=296117>`_ also works.
|
||||
However, be careful. You have to set the correct parameters for the particular CAN hat you're using!
|
||||
|
||||
#. Set the correct oscillator value
|
||||
|
||||
We configure the MCP2515 in section 2.2 of the tutorial, but the hat we recommend uses a 12MHz crystal instead of a 16 MHz crystal.
|
||||
If you're not sure what value to use, the top of the `oscillator <https://en.wikipedia.org/wiki/Crystal_oscillator>`_ will have the value printed on it in MHz.
|
||||
|
||||
My Settings:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
dtparam=spi-on
|
||||
dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25
|
||||
dtoverlay=spi0-hw-cs
|
||||
|
||||
|
||||
#. Use the correct CAN baud rate
|
||||
|
||||
By default, ODrive uses 250 kbps (250000) but the tutorial is using 500 kbps. Make sure you use the value set earlier on the ODrive.
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo ip link set can0 up type can bitrate 250000
|
||||
|
||||
|
||||
Wiring ODrive to CAN
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The CANH and CANL pins on J2 are used for CAN communication. Connect CANH to CANH on all other devices, and CANL to CANL.
|
||||
|
||||
If your ODrive is the "last" (furthest) device on the bus, you can use the on-board 120 Ohm termination resistor by switching the DIP switch to "CAN 120R".
|
||||
Otherwise, add an external resistor.
|
||||
|
||||
|
||||
|
||||
Verifying Communcation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
By default, each ODrive axis will send a heartbeat message at 10Hz. We can confirm our ODrive communication is working by starting the :code:`can0` interface, and then reading from it:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo ip link set can0 up type can bitrate 250000
|
||||
candump can0 -xct z -n 10
|
||||
|
||||
This will read the first 10 messages from the ODrive and stop. If you'd like to see all messages, remove the :code:`-n 10` part (hit CTRL+C to exit).
|
||||
The other flags (x, c, t) are adding extra information, colouring, and a timestamp, respectively.
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
candump can0 -xct z -n 10
|
||||
(000.000000) can0 RX - - 001 [8] 00 00 00 00 01 00 00 00
|
||||
(000.001995) can0 RX - - 021 [8] 00 00 00 00 08 00 00 00
|
||||
(000.099978) can0 RX - - 001 [8] 00 00 00 00 01 00 00 00
|
||||
(000.101963) can0 RX - - 021 [8] 00 00 00 00 08 00 00 00
|
||||
(000.199988) can0 RX - - 001 [8] 00 00 00 00 01 00 00 00
|
||||
(000.201980) can0 RX - - 021 [8] 00 00 00 00 08 00 00 00
|
||||
(000.299986) can0 RX - - 001 [8] 00 00 00 00 01 00 00 00
|
||||
(000.301976) can0 RX - - 021 [8] 00 00 00 00 08 00 00 00
|
||||
(000.399986) can0 RX - - 001 [8] 00 00 00 00 01 00 00 00
|
||||
(000.401972) can0 RX - - 021 [8] 00 00 00 00 08 00 00 00
|
||||
|
||||
|
||||
Alternatively, if you have python can installed (:code:`pip3 install python-can`), you can use the can.viewer script:
|
||||
|
||||
:code:`python3 -m can.viewer -c "can0" -i "socketcan"` which will give you a nice readout.
|
||||
See the `python-can docs <https://python-can.readthedocs.io/en/master/scripts.html#can-viewer>`_ for an example.
|
||||
|
||||
Commanding the ODrive
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Now that we've verified the communication is working, we can try commanding the ODrive.
|
||||
Make sure your ODrive is configured and working properly over USB with:code:`odrivetool` before continuing.
|
||||
See the :ref:`Getting Started Guide <getting-started>` for help with first-time configuration.
|
||||
|
||||
To move the ODrive, we use the command :code:`Set Input Pos`, or cmd ID :code:`0x00C`. First we create a message with this ID, and then "OR" in the axis ID.
|
||||
Then we create an 8-byte array of data with input position that we want, with a float value turned into bytes... this can be a pain though.
|
||||
|
||||
DBC Files
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
A DBC file (.dbc) is a database of all the messages and signals in a CAN protocol.
|
||||
This file can be used with Python cantools to serialize and deserialize messages without having to handle the bitshifting etc yourself.
|
||||
We have generated a .dbc for CANSimple for you!
|
||||
|
||||
* :ref:`CANSimple DBC File <../tools/odrive-cansimple.dbc>`
|
||||
* :ref:`CANSimple DBC Generator Script <../tools/create_can_dbc.py>`
|
||||
|
||||
Instead of manually writing values into the data, we can create a dictionary of signal:value pairs and serialize the data according to the database definition.
|
||||
|
||||
#. Load the database into memory
|
||||
#. Use :code:`encode_message()` to get a byte array representation of data for sending
|
||||
#. Use :code:`decode_message()` to get a dictionary representation of data for receiving
|
||||
|
||||
The :ref:`CAN DBC Example <../tools/can_dbc_example.py>` script shows you how this can be used. This is the recommended method of serializing and deserializing.
|
||||
|
||||
If you're using C++, then you can use the :ref:`CANHelpers <..firmware/communication/../../../Firmware/communication/can/can_helpers.hpp>` single-header library to do this instead, although the DBC file isn't used.
|
||||
85
docs/reStructuredText/can-protocol.rst
Normal file
@@ -0,0 +1,85 @@
|
||||
.. _can-protocol:
|
||||
|
||||
================================================================================
|
||||
CAN Protocol
|
||||
================================================================================
|
||||
|
||||
This document describes the CAN Protocol. For examples of usage, check out our :ref:`CAN Guide! <can-guide>`
|
||||
|
||||
|
||||
Configuring ODrive for CAN
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Configuration of the CAN parameters should be done via USB before putting the device on the bus.
|
||||
|
||||
To set the desired baud rate, use :code:`<odrv>.can.config.baud_rate = <value>`.
|
||||
|
||||
Each axis looks like a separate node on the bus.
|
||||
Thus, they both have the two properties :code:`can_node_id` and :code:`can_node_id_extended`.
|
||||
The node ID can be from 0 to 63 (0x3F) inclusive, or, if extended CAN IDs are used, from 0 to 16777215 (0xFFFFFF).
|
||||
If you want to connect more than one ODrive on a CAN bus, you must set different node IDs for the second ODrive or they will conflict and crash the bus.
|
||||
|
||||
Example Configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.config.can_node_id = 3
|
||||
odrv0.axis1.config.can_node_id = 1
|
||||
odrv0.can.config.baud_rate = 500000
|
||||
odrv0.save_configuration()
|
||||
odrv0.reboot()
|
||||
|
||||
Transport Protocol
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
We've implemented a very basic CAN protocol that we call "CAN Simple" to get users going with ODrive.
|
||||
This protocol is sufficiently abstracted that it is straightforward to add other protocols such as CANOpen, J1939, or Fibre over ISO-TP in the future.
|
||||
Unfortunately, implementing those protocols is a lot of work, and we wanted to give users a way to control ODrive's basic functions via CAN sooner rather than later.
|
||||
|
||||
CAN Frame
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
At its most basic, the CAN Simple frame looks like this:
|
||||
|
||||
* Upper 6 bits - Node ID - max 0x3F (or 0xFFFFFF when using extended CAN IDs)
|
||||
* Lower 5 bits - Command ID - max 0x1F
|
||||
|
||||
To understand how the Node ID and Command ID interact, let's look at an example
|
||||
|
||||
The 11-bit Arbitration ID is setup as follows:
|
||||
|
||||
:code:`can_id = axis_id << 5 | cmd_id`
|
||||
|
||||
For example, an Axis ID of :code:`0x01` with a command of :code:`0x0C` would be result in :code:`0x2C`:
|
||||
|
||||
:code:`0x01 << 5 | 0x0C = 0x2C`
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
All multibyte values are little endian (aka Intel format, aka least significant byte first).
|
||||
|
||||
.. note::
|
||||
|
||||
* These messages are call & response. The Master node sends a message with the RTR bit set, and the axis responds with the same ID and specified payload.
|
||||
* These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple.
|
||||
* These messages can be sent to either address on a given ODrive board.
|
||||
|
||||
|
||||
Interoperability with CANopen
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can deconflict with CANopen like this:
|
||||
|
||||
* :code:`odrv0.axis0.config.can.node_id = 0x010` - Reserves messages 0x200 through 0x21F
|
||||
* :code:`odrv0.axis1.config.can.node_id = 0x018` - Reserves messages 0x300 through 0x31F
|
||||
|
||||
It may not be obvious, but this allows for some compatibility with CANOpen.
|
||||
Although the address space 0x200 and 0x300 correspond to receive PDO base addresses, we can guarantee they will not conflict if all CANopen node IDs are >= 32. E.g.:
|
||||
|
||||
* CANopen nodeID = 35 = 0x23
|
||||
* Receive PDO 0x200 + nodeID = 0x223, which does not conflict with the range [0x200 : 0x21F]
|
||||
|
||||
Be careful that you don't assign too many nodeIDs per PDO group. Four CAN Simple nodes (32*4) is all of the available address space of a single PDO.
|
||||
If the bus is strictly ODrive CAN Simple nodes, a simple sequential Node ID assignment will work fine.
|
||||
|
||||
145
docs/reStructuredText/commands.rst
Normal file
@@ -0,0 +1,145 @@
|
||||
|
||||
.. _commands-doc:
|
||||
|
||||
================================================================================
|
||||
Parameters & Commands
|
||||
================================================================================
|
||||
|
||||
We will use the :code:`<odrv>` as a placeholder for any ODrive object. Every ODrive controller is an ODrive object. In :code:`odrivetool` this is usually :code:`odrv0`. Furthermore we use `<axis>` 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)
|
||||
|
||||
.. contents::
|
||||
:depth: 1
|
||||
:local:
|
||||
|
||||
Per-Axis Commands
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
For the most part, both axes on the ODrive can be controlled independently.
|
||||
|
||||
State Machine
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The current state of an axis is indicated by :ref:`<axis>.current_state <api/odrive.axis-current_state>`.
|
||||
The user can request a new state by assigning a new value to :ref:`<axis>.requested_state <api/odrive.axis-current_state>`.
|
||||
The default state after startup is :code:`AXIS_STATE_IDLE`. A description of all states can be found :ref:`here <api/odrive.axis.axisstate>`.
|
||||
|
||||
.. _commands-startup-procedure:
|
||||
|
||||
Startup Procedure
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
By default the ODrive takes no action at startup and goes to idle immediately.
|
||||
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.
|
||||
|
||||
* :code:`<axis>.config.startup_motor_calibration`
|
||||
* :code:`<axis>.config.startup_encoder_index_search`
|
||||
* :code:`<axis>.config.startup_encoder_offset_calibration`
|
||||
* :code:`<axis>.config.startup_closed_loop_control`
|
||||
|
||||
See :ref:`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 :code:`<axis>.controller.config.control_mode`.
|
||||
Possible values are listed :ref:`here <api/odrive.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 :code:`pos_setpoint`, :code:`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.
|
||||
|
||||
|
||||
Control Commands
|
||||
********************************************************************************
|
||||
|
||||
* :code:`<axis>.controller.input_pos = <turn>`
|
||||
* :code:`<axis>.controller.input_vel = <turn/s>`
|
||||
* :code:`<axis>.controller.input_torque = <torque in Nm>`
|
||||
|
||||
Modes can be selected by changing :code:`<axis>.controller.config.input_mode`.
|
||||
The default input mode is :code:`INPUT_MODE_PASSTHROUGH`.
|
||||
Possible values are listed :ref:`here <api/odrive.controller.inputmode>`.
|
||||
|
||||
System Monitoring Commands
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Encoder Position and Velocity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* View encoder position with :code:`<axis>.encoder.pos_estimate` [turns] or :code:`<axis>.encoder.pos_est_counts` [counts]
|
||||
* View rotational velocity with :code:`<axis>.encoder.vel_estimate` [turn/s] or :code:`<axis>.encoder.vel_est_counts` [count/s]
|
||||
|
||||
Motor Current and Torque Estimation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* View the commanded motor current with :code:`<axis>.motor.current_control.Iq_setpoint` [A]
|
||||
* View the measured motor current with :code:`<axis>.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.
|
||||
|
||||
General System Commands
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Saving the Configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
All variables that are part of a :code:`[...].config` object can be saved to non-volatile memory on the ODrive so they persist after you remove power.
|
||||
The relevant commands are:
|
||||
|
||||
* :code:`<odrv>.save_configuration()`: Stores the configuration to persistent memory on the ODrive.
|
||||
* :code:`<odrv>.erase_configuration()`: Resets the configuration variables to their factory defaults. This also reboots the device.
|
||||
|
||||
Diagnostics
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* :code:`<odrv>.serial_number`: A number that uniquely identifies your device. When printed in upper case hexadecimal (:code:`hex(<odrv>.serial_number).upper()`), this is identical to the serial number indicated by the USB descriptor.
|
||||
* :code:`<odrv>.fw_version_major`, :code:`<odrv>.fw_version_minor`, :code:`<odrv>.fw_version_revision`: The firmware version that is currently running.
|
||||
* :code:`<odrv>.hw_version_major`, :code:`<odrv>.hw_version_minor`, :code:`<odrv>.hw_version_revision`: The hardware version of your ODrive.
|
||||
|
||||
.. _sensorless-setup:
|
||||
|
||||
Setting up Sensorless
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
The ODrive can run without encoder/hall feedback, but there is a minimum speed, usually around a few hundred RPM.
|
||||
In other words, sensorless mode does not support stopping or changing direction!
|
||||
|
||||
Sensorless mode starts by ramping up the motor speed in open loop control and then switches to closed loop control automatically.
|
||||
The sensorless speed ramping parameters are in :code:`axis.config.sensorless_ramp`.
|
||||
The :code:`vel` and :code:`accel` (in [radians/s] and [radians/s^2]) parameters control the speed that the ramp tries to reach and how quickly it gets there.
|
||||
When the ramp reaches :code:`sensorless_ramp.vel`, :code:`controller.input_vel` is automatically set to the same velocity, in [turns/s], and the state switches to closed loop control.
|
||||
|
||||
If your motor comes to a stop after the ramp, try incrementally raising the :code:`vel` parameter.
|
||||
The goal is to be above the minimum speed necessary for sensorless position and speed feedback to converge - this is not well-parameterized per motor.
|
||||
The parameters suggested below work for the D5065 motor, with 270KV and 7 pole pairs.
|
||||
If your motor grinds and skips during the ramp, lower the :code:`accel` parameter until it is tolerable.
|
||||
|
||||
Below are some suggested starting parameters that you can use for the ODrive D5065 motor.
|
||||
Note that you **must** set the :code:`pm_flux_linkage` correctly for sensorless mode to work.
|
||||
Motor calibration and setup must also be completed before sensorless mode will work.
|
||||
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.vel_gain = 0.01
|
||||
odrv0.axis0.controller.config.vel_integrator_gain = 0.05
|
||||
odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL
|
||||
odrv0.axis0.controller.config.vel_limit = <a value greater than axis.config.sensorless_ramp.vel / (2pi * <pole_pairs>)>
|
||||
odrv0.axis0.motor.config.current_lim = 2 * odrv0.axis0.config.sensorless_ramp.current
|
||||
odrv0.axis0.sensorless_estimator.config.pm_flux_linkage = 5.51328895422 / (<pole pairs> * <motor kv>)
|
||||
odrv0.axis0.config.enable_sensorless_mode = True
|
||||
|
||||
|
||||
To start the motor:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
|
||||
|
||||
73
docs/reStructuredText/conf.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file only contains a selection of the most common options. For a full
|
||||
# list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath('.'))
|
||||
sys.path.insert(0, os.path.abspath('./figures'))
|
||||
sys.path.insert(0, os.path.abspath('../exts')) # needed for fibre_autodoc extension
|
||||
sys.path.insert(0, os.path.abspath('../../tools/fibre-tools')) # needed for fibre_autodoc extension
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'ODrive Documentation'
|
||||
copyright = '2021, ODrive Robotics'
|
||||
author = 'ODrive Robotics'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.0'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = ['sphinx_tabs.tabs',
|
||||
'sphinx_copybutton', # copy button for code blocks
|
||||
'sphinx_panels', # dropdown directive
|
||||
'sphinx_rtd_theme', # read the docs theme
|
||||
'sphinx.ext.autodoc', # Generate documentation from Python modules
|
||||
'sphinx.ext.autosummary', # Generate summary tables for Python documentation
|
||||
'sphinx.ext.intersphinx', # Hyperlinks to external projects (such as Python standard library)
|
||||
'fibre_autodoc'# Generate summary tables for Python documentation
|
||||
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
# html_theme = 'alabaster'
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
fibre_interface_files = ['../../Firmware/odrive-interface.yaml']
|
||||
|
||||
autosummary_generate = False
|
||||
|
||||
intersphinx_mapping = {'python': ('https://docs.python.org/3', None)}
|
||||
55
docs/reStructuredText/configuring-eclipse.rst
Normal file
@@ -0,0 +1,55 @@
|
||||
.. _configuring-eclipse:
|
||||
|
||||
================================================================================
|
||||
Setting up Eclipse development environment
|
||||
================================================================================
|
||||
|
||||
Install
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
* Install `Eclipse IDE for C/C++ Developers. <http://www.eclipse.org/downloads/packages/eclipse-ide-cc-developers/neon3>`_
|
||||
* Install the `OpenOCD Eclipse plugin. <http://gnuarmeclipse.github.io/plugins/install/>`_
|
||||
|
||||
Import Project
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
* **File** -> **Import** -> **C/C++** -> Existing Code as Makefile Project
|
||||
* Browse for existing code location, find the OdriveFirmware root.
|
||||
* In the Toolchain options, select `Cross GCC`
|
||||
* Hit **Finish**
|
||||
* Build the project (press :kbd:`ctrl-B`)
|
||||
|
||||
.. figure:: figures/CodeAsMakefile.png
|
||||
:scale: 50 %
|
||||
:alt: Toolchain options
|
||||
|
||||
Load the Launch Configuration
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
* **File** -> **Import** -> **Run/Debug** -> **Launch Configurations** -> **Next**
|
||||
* Highlight (don't tick) the OdriveFirmare folder in the left column
|
||||
* Tick OdriveFirmware.launch in the right column
|
||||
* Hit **Finish**
|
||||
|
||||
.. figure:: figures/ImportLaunch.png
|
||||
:scale: 50 %
|
||||
:alt: Launch Configurations
|
||||
|
||||
Launch!
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
* Make sure the programmer is connected to the board as per :ref:`Flashing the firmware <build-and-flash>`.
|
||||
* Press the down-arrow of the debug symbol in the toolbar, and hit Debug Configurations
|
||||
* You can also hit **Run** -> **Debug Configurations**
|
||||
|
||||
* Highlight the debug configuration you imported, called OdriveFirmware.
|
||||
If you do not see the imported launch configuration rename your project to `ODriveFirmware` or edit the launch configuration to match your project name by unfiltering unavailable projects:
|
||||
|
||||
.. figure:: figures/LaunchConfigFilter.png
|
||||
:scale: 50 %
|
||||
:alt: Launch Configurations Filters
|
||||
|
||||
* Hit **Debug**
|
||||
* Eclipse should flash the board for you and the program should start halted on the first instruction in `Main`
|
||||
* Set beakpoints, step, hit Resume, etc.
|
||||
* Make some cool features! ;D
|
||||
78
docs/reStructuredText/configuring-vscode.rst
Normal file
@@ -0,0 +1,78 @@
|
||||
.. _configuring-vscode:
|
||||
|
||||
================================================================================
|
||||
Configuring Visual Studio Code
|
||||
================================================================================
|
||||
|
||||
VSCode is the recommended IDE for working with the ODrive codebase.
|
||||
It is a light-weight text editor with Git integration and GDB debugging functionality.
|
||||
|
||||
Before doing the VSCode setup, make sure you've installed all of your :ref:`prerequisites. <dev-prereq>`
|
||||
|
||||
Setup Procedure
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
#. Clone the ODrive repository
|
||||
#. `Download VSCode <https://code.visualstudio.com/download>`_
|
||||
#. Open VSCode
|
||||
#. Install extensions. This can be done directly from VSCode (:kbd:`Ctrl` **+** :kbd:`Shift` **+** :kbd:`X`)
|
||||
|
||||
* Required extensions:
|
||||
* C/C++ :code:`ext install ms-vscode.cpptools`
|
||||
* Cortex-Debug :code:`ext install marus25.cortex-debug`
|
||||
* Cortex-Debug: Device Support Pack - STM32F4 :code:`ext install marus25.cortex-debug-dp-stm32f4`
|
||||
|
||||
* Recommended Extensions:
|
||||
* Include Autocomplete
|
||||
* Path Autocomplete
|
||||
* Auto Comment Blocks
|
||||
|
||||
#. Create an environment variable named :code:`ARM_GCC_ROOT` whose value is the location of the :code:`GNU Arm Embedded Toolchain` (.e.g :code:`C:\Program Files (x86)\GNU Tools Arm Embedded\7 2018-q2-update`) that you installed in the prerequisites section of the developer's guide.
|
||||
This is not strictly needed for Linux or Mac, and you can alternatively use the :code:`Cortex-debug: Arm Toolchain Path` setting in VSCode extension settings.
|
||||
#. Relaunch VSCode
|
||||
#. Open the VSCode Workspace file, which is located in the root of the ODrive repository. It is called :code:`ODrive_Workspace.code-workspace`.
|
||||
The first time you open it, VSCode will install some dependencies. If it fails, you may need to `change your proxy settings <https://code.visualstudio.com/docs/getstarted/settings>`_.
|
||||
|
||||
You should now be ready to compile and test the ODrive project.
|
||||
|
||||
Building the Firmware
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* **Terminal** -> **Run Build Task** (:kbd:`Ctrl1` **+** :kbd:`Shift` **+** :kbd:`B`)
|
||||
|
||||
A terminal window will open with your native shell. VSCode is configured to run the command :code:`make -j4` in this terminal.
|
||||
|
||||
Flashing the Firmware
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* **Terminal** -> **Run Task** -> **flash**
|
||||
|
||||
A terminal window will open with your native shell. VSCode is configured to run the command :code:`make flash` in this terminal.
|
||||
|
||||
If the flashing worked, you can connect to the board using the :ref:`odrivetool <odrivetool-startup>`.
|
||||
|
||||
Debugging
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
An extension called Cortex-Debug has recently been released which is designed specifically for debugging ARM Cortex projects.
|
||||
You can read more on Cortex-Debug `here. <https://github.com/Marus/cortex-debug>`_
|
||||
|
||||
Note: If developing on Windows, you should have :code:`arm-none-eabi-gdb` and :code:`openOCD` on your PATH.
|
||||
|
||||
* Make sure you have the Firmware folder as your active folder
|
||||
* Set :code:`CONFIG_DEBUG=true` in the tup.config file
|
||||
* Flash the board with the newest code (starting debug session doesn't do this)
|
||||
* In the **Run** tab (:kbd:`Ctrl` **+** :kbd:`Shift` **+** :kbd:`D`), select "Debug ODrive (Firmware)"
|
||||
* Press **Start Debugging** (or press :kbd:`F5`)
|
||||
* The processor will reset and halt.
|
||||
* Set your breakpoints. Note: you can only set breakpoints when the processor is halted, if you set them during run mode, they won't get applied.
|
||||
* **Continue** (press :kbd:`F5`)
|
||||
* Stepping over/in/out, restarting, and changing breakpoints can be done by first pressing the "pause" (F6) button at the top the screen.
|
||||
* When done debugging, simply stop (:kbd:`Shift` **+** :kbd:`F5`) the debugger. It will kill your openOCD process too.
|
||||
|
||||
Cleaning the Build
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
This sometimes needs to be done if you change branches.
|
||||
|
||||
* Open a terminal (**View** -> **Integrated Terminal**) and enter :code:`make clean`
|
||||
239
docs/reStructuredText/control-modes.rst
Normal file
@@ -0,0 +1,239 @@
|
||||
.. _control-modes-doc:
|
||||
|
||||
================================================================================
|
||||
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 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.
|
||||
|
||||
.. contents::
|
||||
:depth: 1
|
||||
:local:
|
||||
|
||||
|
||||
Filtered Position Control
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Asking the ODrive controller to go as hard as it can to raw setpoints may result in jerky movement.
|
||||
Even if you are using a planned trajectory generated from an external source, if that is sent at a modest frequency, the ODrive may chase each stair in the incoming staircase in a jerky way.
|
||||
In this case, a good starting point for tuning the filter bandwidth is to set it to one half of your setpoint command rate.
|
||||
|
||||
You can use the second order position filter in these cases.
|
||||
Set the filter bandwidth [Hz]:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
axis.controller.config.input_filter_bandwidth = 2.0
|
||||
|
||||
|
||||
Activate the setpoint filter:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
axis.controller.config.input_mode = INPUT_MODE_POS_FILTER
|
||||
|
||||
|
||||
You can now control the position with
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
axis.controller.input_pos = 1
|
||||
|
||||
|
||||
.. figure:: figures/secondOrderResponse.PNG
|
||||
:alt: secondOrderResponse
|
||||
|
||||
Step response of a 1000 to 0 position input with a filter bandwidth of 1.0 Hz
|
||||
|
||||
|
||||
Trajectory Control
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
See the :ref:`Usage <usage>` section for usage details.
|
||||
|
||||
This mode lets you smoothly accelerate, coast, and decelerate the axis from one position to another. With raw position control, the controller simply tries to go to the setpoint as quickly as possible. Using a trajectory lets you tune the feedback gains more aggressively to reject disturbance, while keeping smooth motion.
|
||||
|
||||
.. figure:: figures/TrapTrajPosVel.PNG
|
||||
:alt: TrapTrajPosVel
|
||||
:scale: 130 %
|
||||
|
||||
Position (blue) and velocity (orange) vs. time using trajectory control.
|
||||
|
||||
Parameters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.trap_traj.config.vel_limit = <Float>
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.trap_traj.config.accel_limit = <Float>
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.trap_traj.config.decel_limit = <Float>
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.inertia = <Float>
|
||||
|
||||
|
||||
* :code:`vel_limit` is the maximum planned trajectory speed. This sets your coasting speed.
|
||||
* :code:`accel_limit` is the maximum acceleration in turns / sec^2
|
||||
* :code:`decel_limit` is the maximum deceleration in turns / sec^2
|
||||
* :code:`controller.config.inertia` is a value which correlates acceleration (in turns / sec^2) and motor torque. It is 0 by default. It is optional, but can improve response of your system if correctly tuned. Keep in mind this will need to change with the load / mass of your system.
|
||||
|
||||
|
||||
.. note:: All values should be strictly positive (>= 0).
|
||||
|
||||
|
||||
Keep in mind that you must still set your safety limits as before. It is recommended you set these a little higher ( > 10%) than the planner values, to give the controller enough control authority.
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.motor.config.current_lim = <Float>
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.vel_limit = <Float>
|
||||
|
||||
|
||||
.. _usage:
|
||||
|
||||
Usage
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Make sure you are in position control mode. To activate the trajectory module, set the input mode to trajectory:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.input_mode = INPUT_MODE_TRAP_TRAJ
|
||||
|
||||
|
||||
Simply send a position command to execute the move:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.input_pos = <Float>
|
||||
|
||||
|
||||
Use the :code:`move_incremental` function to move to a relative position.
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.move_incremental(pos_increment, from_goal_point)
|
||||
|
||||
|
||||
To set the goal relative to the current actual position, use :code:`from_goal_point = False`
|
||||
To set the goal relative to the previous destination, use :code:`from_goal_point = True`
|
||||
|
||||
|
||||
You can also execute a move with the :ref:`appropriate ascii command <motor_traj-cmd>`.
|
||||
|
||||
|
||||
Circular Position Control
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
To enable Circular position control, set
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.circular_setpoints = True
|
||||
|
||||
|
||||
This mode is useful for continuous incremental position movement.
|
||||
For example a robot rolling indefinitely, or an extruder motor or conveyor belt moving with controlled increments indefinitely.
|
||||
In the regular position mode, the :code:`input_pos` would grow to a very large value and would lose precision due to floating point rounding.
|
||||
|
||||
In this mode, the controller will try to track the position within only one turn of the motor. Specifically, :code:`input_pos` is expected in the range `[0, 1)`.
|
||||
If the :code:`input_pos` is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value.
|
||||
Note that in this mode :code:`encoder.pos_circular` is used for feedback instead of :code:`encoder.pos_estimate`.
|
||||
|
||||
If you try to increment the axis with a large step in one go that exceeds `1` turn, the motor will go to the same angle around the wrong way.
|
||||
This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a larger circular range.
|
||||
Set
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.circular_setpoints_range = <N>
|
||||
|
||||
|
||||
Choose N to give you an appropriate circular space for your application.
|
||||
|
||||
Velocity Control
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Set the control mode
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL
|
||||
|
||||
|
||||
You can now control the velocity [turn/s] with
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.input_vel = 1
|
||||
|
||||
|
||||
Ramped Velocity Control
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Set the control mode
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL
|
||||
|
||||
|
||||
Set the velocity ramp rate (acceleration in turn/s^2):
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.vel_ramp_rate = 0.5
|
||||
|
||||
Activate the ramped velocity mode:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.input_mode = INPUT_MODE_VEL_RAMP
|
||||
|
||||
|
||||
You can now control the velocity (turn/s) with
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.input_vel = 1
|
||||
|
||||
|
||||
Torque Control
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Set the control mode
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.control_mode = CONTROL_MODE_TORQUE_CONTROL
|
||||
|
||||
|
||||
You can now control the torque (Nm) with
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.input_torque = 0.1
|
||||
|
||||
|
||||
.. note::
|
||||
If you exceed :code:`vel_limit` in torque control mode, the current is reduced.
|
||||
To disable this, set
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.enable_torque_mode_vel_limit = False
|
||||
140
docs/reStructuredText/control.rst
Normal file
@@ -0,0 +1,140 @@
|
||||
.. _control-doc:
|
||||
|
||||
================================================================================
|
||||
Control Structure and Tuning
|
||||
================================================================================
|
||||
|
||||
The motor controller is a cascaded style position, velocity and current control loop, as per the diagram below.
|
||||
When the control mode is set to position control, the whole loop runs.
|
||||
When running in velocity control mode, the position control part is removed and the velocity command is fed directly in to the second stage input.
|
||||
In torque control mode, only the current controller is used.
|
||||
|
||||
.. figure:: figures/controller_with_ff.png
|
||||
:alt: controller_with_ff
|
||||
|
||||
Cascaded position and velocity I loops
|
||||
|
||||
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.
|
||||
|
||||
.. note::
|
||||
The controller has been updated to use `torque` in Newton-meters instead of current at the "system" level.
|
||||
There is a :code:`torque_constant` parameter which converts between torque and current, after which the rest of this explanation still holds.
|
||||
|
||||
|
||||
Position Control Loop
|
||||
--------------------------------------------------------------------------------
|
||||
The position controller is a P loop with a single proportional gain,
|
||||
|
||||
.. math::
|
||||
|
||||
\text{pos_error} &= \text{pos_setpoint} - \text{pos_feedback},
|
||||
|
||||
\text{vel_cmd} &= \text{pos_error} * \text{pos_gain} + \text{vel_feedforward}.
|
||||
|
||||
|
||||
Velocity Control Loop
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
The velocity controller is a PI loop where
|
||||
|
||||
.. math::
|
||||
|
||||
\text{vel_error} &= \text{vel_cmd} - \text{vel_feedback},
|
||||
|
||||
\text{current_integral} &+= \text{vel_error} * \text{vel_integrator gain},
|
||||
|
||||
\text{current_cmd} &= \text{vel_error} * \text{vel_gain} + \text{current_integral} + \text{current_feedforward}.
|
||||
|
||||
|
||||
Current Control Loop
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
The current controller is also a PI loop,
|
||||
|
||||
.. math::
|
||||
|
||||
\text{current_error} &= \text{current_cmd} - \text{current_feedback},
|
||||
|
||||
\text{voltage_integral} &+= \text{current_error} * \text{current_integrator gain},
|
||||
|
||||
\text{voltage_cmd} &= \text{current_error} * \text{current_gain} + \text{voltage_integral} + ...
|
||||
|
||||
& ... + \text{voltage_feedforward} \textbf{ (when we have motor model)}.
|
||||
|
||||
.. note::
|
||||
`current_gain` and `current_integrator_gain` are automatically set according to :code:`motor.config.current_control_bandwidth`
|
||||
|
||||
For more detail refer to `controller.cpp <https://github.com/madcowswe/ODrive/blob/master/Firmware/MotorControl/controller.cpp#L86>`_.
|
||||
|
||||
Controller Details
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
The ultimate output of the controller is the voltage applied to the gate of each FET to deliver current through each coil of the motor.
|
||||
The current through the motor linearly relates to the torque output of the motor.
|
||||
This means that the inputs to the cascaded controller are theoretically the position (angle), velocity (angle/time), and acceleration (angle/time/time) of the motor.
|
||||
Note that when thinking about the controller from the perpective of the physics of the motor you would expect to see the time in the Velocity and Current loops, but it is absent because the time difference between iterations is always 125 microseconds (8kHz).
|
||||
Because the time difference between controller loops is a constant and can simply be wrapped into the controller gains.
|
||||
|
||||
The output of each stage of the controller is clamped before being fed into the next stage.
|
||||
So after the `vel_cmd` is calculated from the position controller, the `vel_cmd` is clamped to the velocity limit.
|
||||
The `torque_cmd` output of the velocity controller is then clamped and fed to the current controller.
|
||||
Oddly enough the controller class does not contain the current controller, but instead the current controller is housed in the motor class due to the complexity of the motor driver schema.
|
||||
|
||||
The feedforward terms available when using the position or velocity control mode are meant to enable better performance when the dynamics of a system are known and the host controller can predict the motion based on the load.
|
||||
A perfect example of this is the use of the trajectory controller that sets the position, velocity, and torque based on the desired position, velocity, and acceleration.
|
||||
If you take a trapezoidal velocity profile for example, you can imagine on the ramp upward the velocity will be increasing over time, while the torque is a non-zero constant.
|
||||
At the flat portion of the profile the velocity will be a non-zero constant, but the acceleration will be zero.
|
||||
This trajectory controller use case uses the cascaded controller with multiple inputs to achieve the desired motion with the best performance.
|
||||
|
||||
.. _control-tuning:
|
||||
|
||||
|
||||
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 (starting) values are:
|
||||
|
||||
* pos_gain [(turn/s) / turn]:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.pos_gain = 20.0
|
||||
|
||||
* vel_gain [Nm/(turn/s)]:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0..controller.config.vel_gain = 0.16
|
||||
|
||||
* vel_integrator_gain [Nm/((turn/s) * s)]:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.vel_integrator_gain = 0.32
|
||||
|
||||
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 :code:`vel_gain` by around 30% per iteration until the motor exhibits some vibration.
|
||||
#. Back down :code:`vel_gain` to 50% of the vibrating value.
|
||||
#. Increase :code:`pos_gain` by around 30% per iteration until you see some overshoot.
|
||||
#. Back down :code:`pos_gain` until you do not have overshoot anymore.
|
||||
#. The integrator can be set to :code:`0.5 * bandwidth * vel_gain`, where :code:`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 :math:`\frac{1}{100ms} = \frac{1}{0.1s} = 10Hz`.
|
||||
In this case you should set
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
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:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
start_liveplotter(lambda:[odrv0.axis0.encoder.pos_estimate, odrv0.axis0.controller.pos_setpoint])
|
||||
479
docs/reStructuredText/developer-guide.rst
Normal file
@@ -0,0 +1,479 @@
|
||||
.. _developer-guide-doc:
|
||||
|
||||
================================================================================
|
||||
ODrive Firmware Developer Guide
|
||||
================================================================================
|
||||
|
||||
.. contents::
|
||||
:depth: 1
|
||||
:local:
|
||||
|
||||
|
||||
This guide is intended for developers who wish to modify the firmware of the ODrive.
|
||||
As such it assumes that you know things like how to use Git, what a compiler is, etc. If that sounds scary, turn around now.
|
||||
|
||||
The official releases are maintained on the `master` branch. However since you are a developer, you are encouraged to use the `devel` branch, as it contains the latest features.
|
||||
|
||||
The project is under active development, so make sure to check the :ref:`Changelog <../CHANGELOG.md>` to keep track of updates.
|
||||
|
||||
.. _dev-prereq:
|
||||
|
||||
Prerequisites
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
The recommended tools for ODrive development are:
|
||||
|
||||
* **make**: Used to invoke tup
|
||||
* **Tup**: The build system used to invoke the compile commands
|
||||
* **ARM GNU Compiler**: For cross-compiling code
|
||||
* **ARM GDB**: For debugging the code and stepping through on the device
|
||||
* **OpenOCD**: For flashing the ODrive with the STLink/v2 programmer
|
||||
* **Python 3**, along with the packages :code:`PyYAML`, :code:`Jinja2` and :code:`jsonschema`: For running the Python tools (:code:`odrivetool`). Also required for compiling firmware.
|
||||
|
||||
See below for specific installation instructions for your OS.
|
||||
|
||||
Depending on what you're gonna do, you may not need all of the components.
|
||||
|
||||
Once you have everything, you can verify the correct installation by running:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
arm-none-eabi-gcc --version
|
||||
arm-none-eabi-gdb --version
|
||||
openocd --version # should be 0.10.0 or later
|
||||
tup --version # should be 0.7.5 or later
|
||||
python --version # should be 3.7 or later
|
||||
|
||||
**Installing Prerequisites**
|
||||
|
||||
.. tabs::
|
||||
.. tab:: Linux (Ubuntu < 20.04)
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo apt-get update
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo apt-get install gcc-arm-embedded
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo apt-get install openocd
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo apt-get install git-lfs
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo add-apt-repository ppa:jonathonf/tup && sudo apt-get update && sudo apt-get install tup
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo apt-get install python3 python3-yaml python3-jinja2 python3-jsonschema
|
||||
|
||||
.. tab:: Linux (Ubuntu >= 20.04)
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo apt install gcc-arm-none-eabi
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo apt install openocd
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo apt install git-lfs
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo apt install tup
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo apt install python3 python3-yaml python3-jinja2 python3-jsonschema
|
||||
|
||||
.. tab:: Arch Linux
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo pacman -S arm-none-eabi-gcc arm-none-eabi-binutils
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo pacman -S arm-none-eabi-gdb
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo pacman -S git-lfs
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo pacman -S tup
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo pacman -S python python-yaml python-jinja python-jsonschema
|
||||
|
||||
* `OpenOCD AUR package <https://aur.archlinux.org/packages/openocd/>`__
|
||||
|
||||
.. tab:: Mac
|
||||
First install `Homebrew <https://brew.sh/>`__ Then you can run these commands in Terminal:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
brew install armmbed/formulae/arm-none-eabi-gcc
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
brew install --cask osxfuse && brew install tup
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
brew install openocd
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
brew install git-lfs
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
pip3 install PyYAML Jinja2 jsonschema
|
||||
|
||||
.. tab:: Windows
|
||||
|
||||
.. note:: make sure these programs are not only installed but also added to your `PATH`.
|
||||
|
||||
Some instructions in this document may assume that you're using a bash command prompt, such as the Windows 10 built-in bash or `Git <https://git-scm.com/download/win>`__ bash.
|
||||
|
||||
* `ARM compiler <https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads>`__
|
||||
|
||||
.. note::
|
||||
|
||||
After installing, create an environment variable named `ARM_GCC_ROOT` whose value is the path you installed to. e.g. :code:`C:\Program Files (x86)\GNU Tools Arm Embedded\7 2018-q2-update`.
|
||||
This variable is used to locate include files for the c/c++ Visual Studio Code extension.
|
||||
|
||||
.. note:: 8-2018-q4-major seems to have a bug on Windows. Please use 7-2018-q2-update.
|
||||
* `Tup <http://gittup.org/tup/index.html>`__
|
||||
* `GNU MCU Eclipse's Windows Build Tools <https://github.com/gnu-mcu-eclipse/windows-build-tools/releases>`__
|
||||
* `Python 3 <https://www.python.org/downloads/>`__
|
||||
* Install Python packages: :code:`pip install PyYAML Jinja2 jsonschema`
|
||||
* `OpenOCD <https://github.com/xpack-dev-tools/openocd-xpack/releases/>`__
|
||||
* `ST-Link/V2 Drivers <https://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-utilities/stsw-link009.html>`__
|
||||
|
||||
|
||||
Configuring the Build
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
To customize the compile time parameters, copy or rename the file :code:`Firmware/tup.config.default` to :code:`Firmware/tup.config` and edit the following parameters in that file:
|
||||
|
||||
* **CONFIG_BOARD_VERSION** The board version you're using. Can be `v3.1`, `v3.2`, `v3.3`, `v3.4-24V`, `v3.4-48V`, `v3.5-24V`, `v3.5-48V`, etc. Check for a label on the upper side of the ODrive to find out which version you have.
|
||||
Some ODrive versions don't specify the voltage: in that case you can read the value of the main capacitors: 120uF are 48V ODrives, 470uF are 24V ODrives.
|
||||
|
||||
* **CONFIG_DEBUG** Defines whether debugging will be enabled when compiling the firmware; specifically the :code:`-g -gdwarf-2` flags.
|
||||
Note that printf debugging will only function if your tup.config specifies the :code:`USB_PROTOCOL` or :code:`UART_PROTOCOL` as stdout and :code:`DEBUG_PRINT` is defined.
|
||||
See the IDE specific documentation for more information.
|
||||
|
||||
You can also modify the compile-time defaults for all :code:`.config` parameters.
|
||||
You will find them if you search for :code:`AxisConfig`, :code:`MotorConfig`, etc.
|
||||
|
||||
.. _build-and-flash:
|
||||
|
||||
Building and Flashing the Firmware
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
#. Run :code:`make` in the :code:`Firmware` directory.
|
||||
#. Connect the ODrive via USB and power it up.
|
||||
#. Flash the firmware using :ref:`odrivetool dfu <firmware-update>`.
|
||||
|
||||
.. _flashing-with-an-stlink:
|
||||
|
||||
Flashing using an STLink/v2
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Connect `GND`, `SWD`, and `SWC` on connector J2 to the programmer.
|
||||
|
||||
.. note:: Always plug in `GND` first!
|
||||
|
||||
* You need to power the board by only **ONE** of the following: VCC(3.3v), 5V, or the main power connection (the DC bus). The USB port (J1) does not power the board.
|
||||
* Run :code:`make flash` in the :code:`Firmware` directory.
|
||||
|
||||
.. note:: If you receive the error `can't find target interface/stlink-v2.cfg` or similar, create and set an environment variable named :code:`OPENOCD_SCRIPTS` to the location of the openocd scripts directory.
|
||||
|
||||
If the flashing worked, you can connect to the board using the :ref:`odrivetool <odrivetool-startup>`.
|
||||
|
||||
Testing
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.. include:: testing.rst
|
||||
|
||||
Debugging
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
If you're using VSCode, make sure you have the Cortex Debug extension, OpenOCD, and the STLink.
|
||||
You can verify that OpenOCD and STLink are working by ensuring you can flash code.
|
||||
Open the ODrive_Workspace.code-workspace file, and start a debugging session (F5).
|
||||
VSCode will pick up the correct settings from the workspace and automatically connect.
|
||||
Breakpoints can be added graphically in VSCode.
|
||||
|
||||
* Run :code:`make gdb`. This will reset and halt at program start. Now you can set breakpoints and run the program. If you know how to use gdb, you are good to go.
|
||||
|
||||
Setting up an IDE
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
For working with the ODrive code you don't need an IDE, but the open-source IDE VSCode is recommended.
|
||||
It is also possible to use Eclipse. If you'd like to go that route, please see the respective configuration document:
|
||||
|
||||
* :ref:`Configuring VSCode <configuring-vscode>`
|
||||
* :ref:`Configuring Eclipse <configuring-eclipse>`
|
||||
|
||||
|
||||
STM32CubeMX
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
This project uses the STM32CubeMX tool to generate startup code and to ease the configuration of the peripherals.
|
||||
You can download it from `here <http://www2.st.com/content/st_com/en/products/development-tools/software-development-tools/stm32-software-development-tools/stm32-configurators-and-code-generators/stm32cubemx.html?icmp=stm32cubemx_pron_pr-stm32cubef2_apr2014&sc=stm32cube-pr2>`___.
|
||||
All CubeMX related files are in :code:`Firmware/Board/v3`.
|
||||
|
||||
You will likely want the pinout for this process. It is available `here <https://docs.google.com/spreadsheets/d/1QXDCs1IRtUyG__M_9WruWOheywb-GhOwFtfPcHuN2Fg/edit#gid=404444347>`__.
|
||||
|
||||
Maintaining Modified Generated Code
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When generating the code, STM32CubeMX will nuke everything except some special sections that they provide.
|
||||
These sections are marked like `USER CODE BEGIN`...`USER CODE END`.
|
||||
We used to try to make sure all edits we made to the generated code would only go in these sections, so some code structrure may reflect that.
|
||||
However over time we realized this will not be tenable, so instead we use git to rebase all changes of the generated code whenever we need to regenerate it.
|
||||
We use two special branches that will help us to do this, they are :code:`STM32CubeMX-start` and :code:`STM32CubeMX-end`.
|
||||
How to use these is shown in the following example.
|
||||
|
||||
.. note::
|
||||
Due to how this rebasing is done, all development that changes the generated code should be done directly on :code:`STM32CubeMX-end`, and not based on :code:`devel`, then follow step 4 below to carry them over to your feature branch. If you did some changes to the generated code based from :code:`devel`, you need to cherry pick just those changes over to :code:`STM32CubeMX-end`.
|
||||
|
||||
#. **Ensuring a clean slate**
|
||||
|
||||
* We do all changes to the STM32CubeMX config and regenerate the code on top of :code:`STM32CubeMX-start`.
|
||||
* :code:`git checkout STM32CubeMX-start`
|
||||
|
||||
* Run stm32cubeMX and load the :code:`Firmware/Board/v3/Odrive.ioc` project file.
|
||||
* If the tool asks if you wish to migrate to a new version, choose to download the old firmware package (unless you want to use the latest libraries)
|
||||
|
||||
* Without changing any settings, press :code:`Project -> Generate code`.
|
||||
* You may need to let it download some drivers and such.
|
||||
* STM32CubeMX may now have a newer version of some of the libraries, so there may be changes to the generated code even though we didn't change any settings. We need to check that everything is still working, and hence check in the changes:
|
||||
* :code:`git config --local core.autocrlf input` - This will tell git that all files should be checked in with LF endings (CubeMX generates CRLF endings).
|
||||
* :code:`git diff` - Ignore the pile of line ending warnings.
|
||||
* If you feel qualified: you can now ispect if CubeMX introduced something stupid. If there were any changes, and they look acceptable, we should commit them:
|
||||
|
||||
* :code:`git commit -am "Run STM32CubeMX v1.21"` - Replace with actual version of CubeMX
|
||||
|
||||
#. **Making Changes to the STM32CubeMX Config**
|
||||
|
||||
* After completing the above steps, make sure the working directory is clean:
|
||||
* :code:`git status` should include "nothing to commit, working tree clean"
|
||||
|
||||
* Make your changes in STM32CubeMX, save the project and generate the code. (:code:`Project -> Generate code`)
|
||||
* :code:`git diff` - Check that the introduced changes are as expected
|
||||
* If everything looks ok, you can commit your changes.
|
||||
|
||||
#. **Rebasing the Modifications to the Generated Code**
|
||||
|
||||
* :code:`git checkout STM32CubeMX-end`
|
||||
* :code:`git rebase STM32CubeMX-start`
|
||||
* Make sure the rebase finishes, fixing any conflicts that may arise
|
||||
|
||||
#. **Merge New STM32CubeMX Code to your Feature Branch**
|
||||
|
||||
Simply merge the new state at:code: `STM32CubeMX-end` into your feature branch.
|
||||
* :code:`git checkout your-feature`
|
||||
* :code:`git merge STM32CubeMX-end`
|
||||
|
||||
#. **Pushing back Upstream**
|
||||
|
||||
* Generate a PR like normal for your feature.
|
||||
* Make sure youhave pushed to the :code:`STM32CubeMX-start` and :code:`STM32CubeMX-end` branches on your fork.
|
||||
* Make a note in your PR to the maintainer that they need to update the STM32CubeMX branches when they merge the PR.
|
||||
|
||||
Troubleshooting
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
:code:`LIBUSB_ERROR_IO` when flashing with the STLink/v2
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Problem:** when I try to flash the ODrive with the STLink using :code:`make flash` I get this error:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
Open On-Chip Debugger 0.10.0
|
||||
Licensed under GNU GPL v2
|
||||
For bug reports, read
|
||||
http://openocd.org/doc/doxygen/bugs.html
|
||||
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
|
||||
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
|
||||
adapter speed: 2000 kHz
|
||||
adapter_nsrst_delay: 100
|
||||
none separate
|
||||
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
|
||||
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
|
||||
Info : clock speed 1800 kHz
|
||||
Error: libusb_open() failed with LIBUSB_ERROR_IO
|
||||
Error: open failed
|
||||
in procedure 'init'
|
||||
in procedure 'ocd_bouncer'
|
||||
|
||||
**Solution:**
|
||||
This happens from time to time.
|
||||
|
||||
#. Unplug the STLink and all ODrives from your computer
|
||||
#. Power off the ODrive you're trying to flash
|
||||
#. Plug in the STLink into your computer
|
||||
#. Power on the ODrive
|
||||
#. Run :code:`make flash` again
|
||||
|
||||
:code:`Cannot identify target as a STM32 family` when flashing using openocd
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Problem:** When I try to flash ODrive v4.1 with :code:`make flash` then I get:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
[...]
|
||||
** Programming Started **
|
||||
auto erase enabled
|
||||
Info : device id = 0x10006452
|
||||
Warn : Cannot identify target as a STM32 family.
|
||||
Error: auto_probe failed
|
||||
embedded:startup.tcl:487: Error: ** Programming Failed **
|
||||
in procedure 'program'
|
||||
in procedure 'program_error' called at file "embedded:startup.tcl", line 543
|
||||
at file "embedded:startup.tcl", line 487
|
||||
|
||||
|
||||
**Solution:**
|
||||
Compile and install a recent version of openocd from source.
|
||||
The latest official release (0.10.0 as of Nov 2020) doesn't support the STM32F722 yet.
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo apt-get install libtool libusb-1.0
|
||||
git clone https://git.code.sf.net/p/openocd/code openocd
|
||||
cd openocd/
|
||||
./bootstrap
|
||||
./configure --enable-stlink
|
||||
make
|
||||
sudo make install
|
||||
|
||||
|
||||
Documentation
|
||||
--------------------------------------------------------------------------------
|
||||
.. admonition:: TODO
|
||||
|
||||
**Documentation refactor in progress, changing significantly**
|
||||
|
||||
* prerequisites
|
||||
* Install Sphinx: :code:`pip install -U sphinx`
|
||||
* Install packages: :code:`pip install sphinx-copybutton sphinx-panels sphinx-rtd-theme`
|
||||
|
||||
* Run :code:`make html` within the :kbd:`./docs/reStructuredText` folder
|
||||
* Open :kbd:`./docs/reStructuredText/_build/index.html` to view
|
||||
|
||||
.. All `*.md` files in the `docs/` directory of the master branch are served up by GitHub Pages on `this domain <https://docs.odriverobotics.com>`__.
|
||||
|
||||
.. * Theme: `minimal https://github.com/pages-themes/minimal>`__ by `orderedlist](https://github.com/orderedlist)
|
||||
.. * HTML layout: `docs/_layouts/default.html`
|
||||
.. * CSS style: `docs/assets/css/styles.scss`
|
||||
.. * Site index: `docs/_data/index.yaml`
|
||||
|
||||
.. To run the docs server locally:
|
||||
|
||||
.. ```bash
|
||||
.. cd docs
|
||||
.. gem install bundler # 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
|
||||
.. ```
|
||||
|
||||
.. On Ubuntu 18.04, prerequisites are: `ruby ruby-dev zlib1g-dev`.
|
||||
|
||||
.. _modifying-libfibre:
|
||||
|
||||
Modifying libfibre
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
If you need to modify libfibre add :code:`CONFIG_BUILD_LIBFIBRE=true` to your tup.config and rerun :code:`make`.
|
||||
After this you can start :code:`odrivetool` (on your local PC) and it will use the updated libfibre.
|
||||
|
||||
To cross-compile libfibre for the Raspberry Pi, run :code:`make libfibre-linux-armhf` or :code:`make libfibre-all`.
|
||||
This will require a docker container. See ;:ref:`fibre-cpp readme <../Firmware/fibre-cpp/README.md>` for details.
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
docker run -it -v "$(pwd)":/build -v /tmp/build:/build/build -w /build fibre-compiler configs/linux-armhf.config
|
||||
|
||||
If you're satisfied with the changes don't forget to generate binaries for all
|
||||
supported systems using :code:`make libfibre-all`.
|
||||
|
||||
Releases
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
We use GitHub Releases to provide firmware releases.
|
||||
|
||||
#. Cut off the changelog to reflect the new release
|
||||
#. Merge the release candidate into master.
|
||||
#. Push a (lightweight) tag to the master branch. Follow the existing naming convention.
|
||||
#. If you changed something in libfibre, regenerate the binaries using :code:`make libfibre-all`.
|
||||
See :ref:`Modifying libfibre <modifying-libfibre>` for details.
|
||||
#. Push the python tools to PyPI (see setup.py for details).
|
||||
#. Edit the release on GitHub to add a title and description (copy&paste from changelog).
|
||||
|
||||
Code Maintenance Notes
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
The cortex M4F processor has hardware single precision float unit. However double precision operations are not accelerated, and hence should be avoided.
|
||||
The following regex is helpful for cleaning out double constants:
|
||||
|
||||
find:
|
||||
|
||||
.. code::
|
||||
|
||||
([-+]?[0-9]+\.[0-9]+(?:[eE][-+]?[0-9]+)?)([^f0-9e])
|
||||
|
||||
replace:
|
||||
|
||||
.. code::
|
||||
|
||||
\1f\2
|
||||
|
||||
Notes for Contributors
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
In general the project uses the `Google C++ Style Guide <https://google.github.io/styleguide/cppguide.html>`__ with a few exceptions:
|
||||
|
||||
* The default indentation is 4 spaces.
|
||||
* The 80 character limit is not very strictly enforced, merely encouraged.
|
||||
* The file extensions `*.cpp` and `*.hpp` are used instead of `*.cc` and `*.h`.
|
||||
|
||||
Your help is welcome! However before you start working on a feature/change that will take you a non-negligible amount of time and that you plan to upstream please discuss your plans with us on GitHub or Discord.
|
||||
This will ensure that your implementation is in line with the direction that ODrive is going.
|
||||
|
||||
When filing a PR please go through this checklist:
|
||||
|
||||
* Make sure you adhere to the same coding style that we use (see note above).
|
||||
* Update CHANGELOG.md.
|
||||
* If you removed/moved/renamed things in :code:`odrive-interface.yaml` make sure to add corresponding bullet points tp the "API migration notes" section in the changelog. Use git to compare against the :code:`devel` branch.
|
||||
* Also, for each removed/moved/renamed API item use your IDE's search feature to search for occurrences of this name. Update the places you found (this will usually be documentation and test scripts).
|
||||
* If you added things to :code:`odrive-interface.yaml` make sure the new things have decent documentation in the YAML file. We don't expect 100% coverage but use good sense of what to document.
|
||||
* Make sure your PR doesn't contain spurious changes that unnecessarily add or remove whitespace. These add noise and make the reviewer's lifes harder.
|
||||
* If you changed any enums in :code:`odrive-interface.yaml`, make sure you update :ref:`enums.py <../tools/odrive/enums.py>` and :ref:`ODriveEnums.h <../Arduino/ODriveArduino/ODriveEnums.h>`.
|
||||
The file includes instructions on how to do this. Check the diff to verify that none of the existing enumerators changed their value.
|
||||
362
docs/reStructuredText/encoders.rst
Normal file
@@ -0,0 +1,362 @@
|
||||
================================================================================
|
||||
Encoders
|
||||
================================================================================
|
||||
|
||||
Known and Supported Encoders
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Be sure to read the `ODrive Encoder Guide <https://docs.google.com/spreadsheets/d/1OBDwYrBb5zUPZLrhL98ezZbg94tUsZcdTuwiVNgVqpU>`_.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
All encoder types supported by ODrive require that you do some sort of encoder calibration. This requires the following:
|
||||
|
||||
* Selecting an encoder and mounting it to your motor
|
||||
* Choosing an interface (e.g., AB, ABI or SPI)
|
||||
* Connecting the pins to the odrive
|
||||
* Loading the correct odrive firmware (the default will work in many cases)
|
||||
* Motor calibration
|
||||
* Saving the settings in the odrive for correct bootup
|
||||
|
||||
.. _encoder-without-index-signal:
|
||||
|
||||
Encoder Without Index Signal
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
During encoder offset calibration the rotor must be allowed to rotate without any biased load during startup.
|
||||
That means mass and weak friction loads are fine, but gravity or spring loads are not okay.
|
||||
|
||||
In the :code:`odrivetool`, run
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION
|
||||
|
||||
|
||||
To verify everything went well, check the following variables:
|
||||
|
||||
* :code:`<axis>.error` should be 0.
|
||||
* :code:`<axis>.encoder.config.phase_offset` - This should print a number, like -326 or 1364.
|
||||
* :code:`<axis>.encoder.config.direction` - This should print 1 or -1.
|
||||
|
||||
.. _encoder-with-index-signal:
|
||||
|
||||
Encoder With Index Signal
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you have an encoder with an index (Z) signal, you can avoid doing the offset calibration on every startup, and instead use the index signal to re-sync the encoder to a stored calibration.
|
||||
|
||||
Below are the steps to do the one-time calibration and configuration.
|
||||
Note that you can follow these steps with one motor at a time, or all motors together, as you wish.
|
||||
|
||||
* Since you will only do this once, it is recommended that you mechanically disengage the motor from anything other than the encoder, so that it can spin freely.
|
||||
* Set :code:`<axis>.encoder.config.use_index` to `True`.
|
||||
* Run :code:`<axis>.requested_state = AXIS_STATE_ENCODER_INDEX_SEARCH`.
|
||||
This will make the motor turn in one direction until it finds the encoder index.
|
||||
* Follow the calibration instructions for an :ref:`encoder without index signal <encoder-without-index-signal>`.
|
||||
* Set :code:`<axis>.encoder.config.pre_calibrated` to `True` to confirm that the offset is valid with respect to the index pulse.
|
||||
* If you would like to search for the index at startup, set :code:`<axis>.config.startup_encoder_index_search` to `True`.
|
||||
* If you'd rather do it manually, just run :code:`<axis>.requested_state = AXIS_STATE_ENCODER_INDEX_SEARCH` on every bootup.
|
||||
* If you are looking to start your machine as quickly as possible on bootup, also set :code:`<axis>.motor.config.pre_calibrated` to `True` to save the current motor calibration and avoid doing it again on bootup.
|
||||
* Save the configuration by typing :code:`<odrv>.save_configuration()` :kbd:`Enter`.
|
||||
|
||||
That's it, now on every reboot the motor will turn in one direction until it finds the encoder index.
|
||||
|
||||
* If your motor has problems reaching the index location due to the mechanical load, you can increase :code:`<axis>.motor.config.calibration_current`.
|
||||
|
||||
Reversing Index Search
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sometimes you would like the index search to only happen in a particular direction (the reverse of the default).
|
||||
Instead of swapping the motor leads, you can ensure that the following three values are negative:
|
||||
|
||||
* :code:`<axis>.config.calibration_lockin.vel`
|
||||
* :code:`<axis>.config.calibration_lockin.accel`
|
||||
* :code:`<axis>.config.calibration_lockin.ramp_distance`
|
||||
|
||||
|
||||
.. important::
|
||||
Your motor should find the same rotational position when the ODrive performs an index search if the index signal is working properly.
|
||||
This means that the motor should spin, and stop at the same position if you have set :code:`<axis>.config.startup_encoder_index_search` so the search starts on reboot, or you if call the command :code:`<axis>.requested_state = AXIS_STATE_ENCODER_INDEX_SEARCH` after reboot.
|
||||
You can test this. Send the :code:`<odrv>.reboot()` command, and while it's rebooting turn your motor, then make sure the motor returns back to the correct position each time when it comes out of reboot.
|
||||
Try this procedure a couple of times to be sure.
|
||||
|
||||
.. _encoders-hall-effect:
|
||||
|
||||
Hall Effect Encoders
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Hall effect encoders can also be used with ODrive. The encoder CPR should be set to `6 * <# of motor pole pairs>`.
|
||||
Due to the low resolution of hall effect encoders compared to other types of encoders, low speed performance will be worse than other encoder types.
|
||||
|
||||
When the encoder mode is set to hall feedback, the pinout on the encoder port is as follows:
|
||||
|
||||
|
||||
.. list-table::
|
||||
:widths: 25 25
|
||||
:header-rows: 1
|
||||
|
||||
* - Label on ODrive
|
||||
- Hall feedback
|
||||
* - A
|
||||
- Hall A
|
||||
* - B
|
||||
- Hall B
|
||||
* - Z
|
||||
- Hall C
|
||||
|
||||
To use hall effect encoders, the calibration sequence is different than incremental or absolute encoders.
|
||||
You must first run :code:`AXIS_STATE_ENCODER_HALL_POLARITY_CALIBRATION` before :code:`AXIS_STATE_ENCODER_OFFSET_CALIBRATION` The hall polarity calibration will automatically determine the order and polarity of the hall signals.
|
||||
When using :code:`AXIS_STATE_FULL_CALIBRATION_SEQUENCE`, these steps are automatically used if the encoder is set to hall mode.
|
||||
|
||||
Startup Sequence Notes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The following are variables that MUST be set up for your encoder configuration. Your values will vary depending on your encoder:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.encoder.config.cpr = 8192
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.encoder.config.mode = ENCODER_MODE_INCREMENTAL
|
||||
|
||||
The following are examples of values that can impact the success of calibration.
|
||||
These are not all of the variables you have to set for startup.
|
||||
Only change these when you understand why they are needed; your values will vary depending on your setup:
|
||||
|
||||
* :code:`<axis>.motor.config.motor_type = MOTOR_TYPE_HIGH_CURRENT` The type of motor you have. Valid choices are high current or gimbal.
|
||||
* :code:`<axis>.encoder.config.calib_range = 0.05` Helps to relax the accuracy of encoder counts during calibration
|
||||
* :code:`<axis>.motor.config.calibration_current = 10.0` The motor current used for calibration. For large motors, this value can be increased to overcome friction and cogging.
|
||||
* :code:`<axis>.motor.config.resistance_calib_max_voltage = 12.0` Max motor voltage used for measuring motor resistance. For motor calibration, it must be possible for the motor current to reach the calibration current without the applied voltage exceeding this config setting.
|
||||
* :code:`<axis>.controller.config.vel_limit = 5` [turn/s] low values result in the spinning motor stopping abruptly during calibration.
|
||||
|
||||
Lots of other values can get you. It's a process. Thankfully there are a lot of good people that will help you debug calibration problems.
|
||||
|
||||
If calibration works, congratulations.
|
||||
|
||||
Now try:
|
||||
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
|
||||
<axis>.controller.input_vel = 1.5
|
||||
|
||||
let it loop a few times and then set:
|
||||
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.requested_state = AXIS_STATE_IDLE
|
||||
|
||||
Do you still have no errors? Awesome. Now, setup the motor and encoder to use known calibration values.
|
||||
This allows you to skip motor calibration and encoder offset calibration before using closed loop control.
|
||||
Note that this only works if you are using an absolute encoder or the encoder index input (see "Encoder with index signal" above).
|
||||
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.encoder.config.pre_calibrated = True
|
||||
<axis>.motor.config.pre_calibrated = True
|
||||
|
||||
And see if ODrive agrees that the calibration worked by just running
|
||||
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.encoder.config.pre_calibrated
|
||||
|
||||
.. note:: (using no "= True" ). Make sure that 'pre_calibrated' is in fact True.
|
||||
|
||||
Also, if you have calibrated and encoder.pre_calibrated is equal to true, and you had no errors so far, run this:
|
||||
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.save_configuration()
|
||||
odrv0.reboot()
|
||||
|
||||
and now see if after a reboot you can run:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.requested_state = AXIS_STATE_ENCODER_INDEX_SEARCH
|
||||
|
||||
without getting errors.
|
||||
|
||||
What Happens if Calibration Fails
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
There are subtle ways that encoder problems will impact your ODrive.
|
||||
For example, ODrive may not complete the calibrate sequence when you go to:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE
|
||||
|
||||
Or, ODrive may complete the calibrate sequence after:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE
|
||||
|
||||
but then it fails after you go to:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
|
||||
|
||||
Or ODrive may just vibrate in an entertaining way. See `this video. <https://www.youtube.com/watch?v=gaRUmwvSyAs>`_
|
||||
|
||||
Encoder Testing
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
There are things you can test to make sure your encoder is properly connected.
|
||||
:code:`shadow_count` tracks encoder motion, even before the encoder or motor are calibrated.
|
||||
If your encoder is working, you should see this value change when you turn the motor.
|
||||
Run the command:
|
||||
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.encoder.shadow_count
|
||||
|
||||
and look at your value. Then turn your motor by hand and see if that value changes. Also, notice that the command:
|
||||
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.encoder.config.cpr = 4000
|
||||
|
||||
must reflect the number of counts ODrive receives after one complete turn of the motor.
|
||||
So use shadow_count to test if that is working properly.
|
||||
|
||||
You will probably never be able to properly debug if you have problems unless you use an oscilloscope.
|
||||
If you have one, try the following:
|
||||
Connect to the AB pins, see if you get square waves as you turn the motor.
|
||||
Connect to the I pin, see if you get a pulse on a complete rotation. Sometimes this is hard to see.
|
||||
|
||||
If you are using SPI, use a logic analyzer and connect to the CLK, MISO, and CS pins.
|
||||
Set a trigger for the CS pin and ensure that the encoder position is being sent and is increasing/decreasing as you spin the motor.
|
||||
There is extremely cheap hardware that is supported by `Sigrok <https://sigrok.org/>`_ for protocol analysis.
|
||||
|
||||
|
||||
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 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 and 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 noise on the Z input of ODrive.
|
||||
Symptoms for this problem include:
|
||||
|
||||
* difficulty with :code:`requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE`, where your calibration sequence may not complete
|
||||
* strange behavior after performing :code:`<odrv>.save_configuration()` and :code:`<odrv>.reboot()`
|
||||
* when performing an index_search, the motor does not return to the same position each time.
|
||||
|
||||
One easy step that **might** fix the noise on the Z input is to solder a 22nF-47nF capacitor to the Z pin and the GND pin on the underside of the ODrive board.
|
||||
|
||||
Hall Feedback Pinout
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
If position accuracy is not a concern, you can use A/B/C hall effect encoders for position feedback.
|
||||
|
||||
To use this mode, configure the corresponding encoder mode: :code:`<encoder>.config.mode = ENCODER_MODE_HALL`.
|
||||
Configure the corresponding GPIOs as digital inputs:
|
||||
|
||||
For encoder 0:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.config.gpio9_mode = GPIO_MODE_DIGITAL
|
||||
<odrv>.config.gpio10_mode = GPIO_MODE_DIGITAL
|
||||
<odrv>.config.gpio11_mode = GPIO_MODE_DIGITAL
|
||||
|
||||
For encoder 1:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.config.gpio12_mode = GPIO_MODE_DIGITAL
|
||||
<odrv>.config.gpio13_mode = GPIO_MODE_DIGITAL
|
||||
<odrv>.config.gpio14_mode = GPIO_MODE_DIGITAL
|
||||
|
||||
|
||||
In this mode, the pinout on the encoder port is as follows:
|
||||
|
||||
.. list-table::
|
||||
:widths: 25 25
|
||||
:header-rows: 1
|
||||
|
||||
* - Label on ODrive
|
||||
- Hall feedback
|
||||
* - A
|
||||
- Hall A
|
||||
* - B
|
||||
- Hall B
|
||||
* - Z
|
||||
- Hall C
|
||||
|
||||
|
||||
SPI Encoders
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Apart from (incremental) quadrature encoders, ODrive also supports absolute SPI encoders (since firmware v0.5).
|
||||
These usually measure an absolute angle.
|
||||
This means you don't need to repeat the encoder calibration after every ODrive reboot.
|
||||
Currently, the following modes are supported:
|
||||
|
||||
* **CUI protocol**: Compatible with the AMT23xx family (AMT232A, AMT232B, AMT233A, AMT233B).
|
||||
* **AMS protocol**: Compatible with AS5047P and AS5048A.
|
||||
|
||||
Some of these chips come with evaluation boards that can simplify mounting the chips to your motor.
|
||||
For our purposes if you are using an evaluation board you should select the settings for 3.3v.
|
||||
|
||||
.. note::
|
||||
The AMT23x family has a hardware bug that causes them to not properly tristate the MISO line.
|
||||
To use them with ODrive, there are two workarounds.
|
||||
One is to sequence power to the encoder a second or two after the ODrive recieves power.
|
||||
This allows 1 encoder to be used without issue.
|
||||
Another solution is to add a tristate buffer, such as the 74AHC1G125SE, on the MISO line between the ODrive and each AMT23x encoder.
|
||||
Tie the enable pin on the buffer to the CS line for the respective encoder.
|
||||
This allows for more than one AMT23x encoder, or one AMT23x and another SPI encoder, to be used at the same time.
|
||||
|
||||
#. Connect the encoder to the ODrive's SPI interface:
|
||||
|
||||
* The encoder's SCK, MISO (aka "DATA" on CUI encoders), MOSI (if present on the encoder), GND and 3.3V should connect to the ODrive pins with the same label.
|
||||
If you want to save a wire with AMS encoders, you can also connect the encoder's MOSI to the encoder's VDD instead.
|
||||
* The encoder's Chip Select (aka nCS/CSn) can be connected to any of the ODrive's GPIOs (caution: GPIOs 1 and 2 are usually used by UART).
|
||||
|
||||
If you are having calibration problems, make sure that your magnet is centered on the axis of rotation on the motor.
|
||||
Some users report that this has a significant impact on calibration.
|
||||
Also make sure that your magnet height is within range of the spec sheet.
|
||||
|
||||
#. In :code:`odrivetool`, run:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.encoder.config.abs_spi_cs_gpio_pin = 4 # or which ever GPIO pin you choose
|
||||
<axis>.encoder.config.mode = ENCODER_MODE_SPI_ABS_CUI # or ENCODER_MODE_SPI_ABS_AMS
|
||||
<axis>.encoder.config.cpr = 2**14 # or 2**12 for AMT232A and AMT233A
|
||||
<odrv>.save_configuration()
|
||||
<odrv>.reboot()
|
||||
|
||||
#. Run the :ref:`offset calibration <encoder-without-index-signal>` and then save the calibration with :code:`<odrv>.save_configuration()`.
|
||||
The next time you reboot, the encoder should be immediately ready.
|
||||
|
||||
Sometimes the encoder takes longer than the ODrive to start, in which case you need to clear the errors after every restart.
|
||||
|
||||
If you are having calibration problems - make sure your magnet is centered on the axis of rotation on the motor, some users report this has a significant impact on calibration.
|
||||
Also make sure your magnet height is within range of the spec sheet.
|
||||
|
||||
222
docs/reStructuredText/endstops.rst
Normal file
@@ -0,0 +1,222 @@
|
||||
================================================================================
|
||||
Endstops and Homing
|
||||
================================================================================
|
||||
|
||||
By default, the ODrive assumes that your motor encoder's zero position is the same as your machine's zero position, but in real life this is rarely the case.
|
||||
In these systems it is useful to allow your motor to move until a physical or electronic device orders the system to stop.
|
||||
That `endstop` can be used as a known reference point. Once the ODrive has hit that position it may then want to move to a final zero, or `home`, position.
|
||||
The process of finding your machine's zero position is known as `homing`.
|
||||
|
||||
ODrive supports the use of its GPIO pins to connect to phyiscal limit switches or other sensors that can serve as endstops.
|
||||
Before you can home your machine, you must be able to adequately control your motor in :code:`AXIS_STATE_CLOSED_LOOP_CONTROL`.
|
||||
|
||||
Endstop Configuration
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Each axis supports two endstops: :code:`min_endstop` and :code:`max_endstop`.
|
||||
For each endstop, the following properties are accessible through :code:`odrivetool`:
|
||||
|
||||
.. list-table::
|
||||
:widths: 25 25 25
|
||||
:header-rows: 1
|
||||
|
||||
* - Name
|
||||
- Type
|
||||
- Default
|
||||
* - gpio_num
|
||||
- int
|
||||
- 0
|
||||
* - offset
|
||||
- float
|
||||
- 0.0
|
||||
* - debounce_ms
|
||||
- float
|
||||
- 50.0
|
||||
* - enabled
|
||||
- boolean
|
||||
- false
|
||||
* - is_active_high
|
||||
- boolean
|
||||
- false
|
||||
|
||||
|
||||
:code:`gpio_num`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The GPIO pin number, according to the silkscreen labels on ODrive. Set with these commands:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.<axis>.max_endstop.config.gpio_num = <1, 2, 3, 4, 5, 6, 7, 8>
|
||||
<odrv>.<axis>.min_endstop.config.gpio_num = <1, 2, 3, 4, 5, 6, 7, 8>
|
||||
|
||||
|
||||
:code:`enabled`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Enables/disables detection of the endstop. If disabled, homing and e-stop cannot take place. Set with:
|
||||
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.<axis>.max_endstop.config.enabled = <True, False>
|
||||
<odrv>.<axis>.min_endstop.config.enabled = <True, False>
|
||||
|
||||
|
||||
:code:`offset`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is the position of the endstops on the relevant axis, in turns.
|
||||
For example, if you want a position command of `0` to represent a position 3 turns away from the endstop, the offset would be `-3.0` (because the endstop is located at axis position `-3.0`).
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.<axis>.min_endstop.config.offset = <int>
|
||||
|
||||
|
||||
This setting is only used for homing. Only the offset of the :code:`min_endstop` is used.
|
||||
|
||||
:code:`debounce_ms`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The debouncing time for this endstop. Most switches exhibit some sort of bounce, and this setting will help prevent the switch from triggering repeatedly.
|
||||
It works for both HIGH and LOW transitions, regardless of the setting of :code:`is_active_high`.
|
||||
Debouncing is a good practice for digital inputs, read up on it `here <https://en.wikipedia.org/wiki/Switch>`_. :code:`debounce_ms` has units of miliseconds.
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.<axis>.max_endstop.config.debounce_ms = <Float>
|
||||
<odrv>.<axis>.min_endstop.config.debounce_ms = <Float>
|
||||
|
||||
|
||||
:code:`is_active_high`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is how you configure the endstop to be either "NPN" or "PNP".
|
||||
An "NPN" configuration would be :code:`is_active_high = False` whereas a PNP configuration is `is_active_high = True`.
|
||||
Refer to the following table for more information:
|
||||
|
||||
Typically configuration **1** or **3** is preferred when using mechanical switches as the most common failure mode leaves the switch open.
|
||||
|
||||
GPIO Configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The GPIOs that are used for the endstops need to be configured according to the diagram below.
|
||||
|
||||
Assuming your endstop is connected to GPIO X:
|
||||
|
||||
* Configuration 1, 2: :code:`<odrv>.config.gpioX_mode = GPIO_MODE_DIGITAL_PULL_DOWN`
|
||||
* Configuration 3, 4: :code:`<odrv>.config.gpioX_mode = GPIO_MODE_DIGITAL_PULL_DOWN`
|
||||
|
||||
.. figure:: figures/Endstop_configuration.png
|
||||
:scale: 50 %
|
||||
:alt: Endstop configuration
|
||||
|
||||
Example
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If we want to configure a 3D printer-style (configuration 4) minimum endstop for homing on GPIO 5 and we want our motor to move away from the endstop about a quarter turn with a 8192 cpr encoder, we would set:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.config.gpio5_mode = GPIO_MODE_DIGITAL
|
||||
<odrv>.<axis>.min_endstop.config.gpio_num = 5
|
||||
<odrv>.<axis>.min_endstop.config.is_active_high = False
|
||||
<odrv>.<axis>.min_endstop.config.offset = -1.0*(8912/4)
|
||||
<odrv>.<axis>.min_endstop.config.enabled = True
|
||||
<odrv>.config.gpio5_mode = GPIO_MODE_DIGITAL_PULL_UP
|
||||
|
||||
|
||||
Testing The Endstops
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Once the endstops are configured you can test your endstops for correct functionality.
|
||||
Try activating your endstops and check the states of these variables through odrivetool:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.<axis>.max_endstop.endstop_state
|
||||
<odrv>.<axis>.min_endstop.endstop_state
|
||||
|
||||
|
||||
A state of `True` means the switch is pressed. A state of `False` means the switch is NOT pressed.
|
||||
As simple as that. Give it a try. Click your switches, or put a magnet on your hall switch and see if the states change.
|
||||
|
||||
After testing, don't forget to save and reboot:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.save_configuration()
|
||||
<odrv>.reboot()
|
||||
|
||||
Homing
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
There is one additional configuration parameter in :code:`controller.config` specifically for the homing process:
|
||||
|
||||
.. list-table::
|
||||
:widths: 25 25 25
|
||||
:header-rows: 1
|
||||
|
||||
* - Name
|
||||
- Type
|
||||
- Default
|
||||
* - homing_speed
|
||||
- float
|
||||
- 0.25f
|
||||
|
||||
|
||||
|
||||
:code:`homing_speed` is the axis travel speed during homing, in [turns/second].
|
||||
If you are using SPI based encoders and the axis is homing in the wrong direction, you can enter a negative value for the homing speed and a negative value for the minimum endstop offset.
|
||||
|
||||
Set the homing speed to 0.25 turns / sec:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.homing_speed = 0.25
|
||||
|
||||
|
||||
Performing the Homing Sequence
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Homing is possible once the ODrive has closed-loop control over the axis.
|
||||
To trigger homing, we must enter:code:`AXIS_STATE_HOMING`. This starts the homing sequence, which works as follows:
|
||||
|
||||
#. The axis switches to :code:`INPUT_MODE_VEL_RAMP`
|
||||
#. The axis ramps up to :code:`homing_speed` in the direction of :code:`min_endstop`
|
||||
#. The axis presses the :code:`min_endstop`
|
||||
#. The axis switches to :code:`INPUT_MODE_TRAP_TRAJ`
|
||||
#. The axis moves to the home position in a controlled manner
|
||||
|
||||
It requires quite a few settings in addition to the endstop settings:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.<axis>.controller.config.vel_ramp_rate
|
||||
<odrv>.<axis>.trap_traj.config.vel_limit
|
||||
<odrv>.<axis>.trap_traj.config.accel_limit
|
||||
<odrv>.<axis>.trap_traj.config.decel_limit
|
||||
|
||||
|
||||
We realize this is a little excessive and we will work towards minimizing the setup, but this works well for smooth and reliable behaviour for now.
|
||||
|
||||
Homing at Startup
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is possible to configure the odrive to enter homing immediately after startup.
|
||||
To enable homing at startup, the following must be configured:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.<axis>.config.startup_homing = True
|
||||
|
||||
|
||||
Additional Endstop Devices
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
In addition to phyiscal switches there are other options for wiring up your endstops - you will have to work out the details of connecting your device but here are some suggested approaches:
|
||||
|
||||
.. figure:: figures/endstop_figure.png
|
||||
:scale: 100 %
|
||||
:alt: endstop figure
|
||||
@@ -0,0 +1,11 @@
|
||||
ODrive Reference
|
||||
================
|
||||
|
||||
.. fibreclass:: com.odriverobotics.ODrive
|
||||
|
||||
|
||||
.. fibrenamespace:: com.odriverobotics.ODrive
|
||||
:bitfields:
|
||||
:enums:
|
||||
:classes:
|
||||
:namespaces:
|
||||
BIN
docs/reStructuredText/figures/CAN_Bus_Drawing.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
docs/reStructuredText/figures/CodeAsMakefile.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
docs/reStructuredText/figures/Endstop_configuration.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
docs/reStructuredText/figures/ImportLaunch.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
docs/reStructuredText/figures/LaunchConfigFilter.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/reStructuredText/figures/ODriveBasicWiring.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
docs/reStructuredText/figures/TrapTrajPosVel.PNG
Normal file
|
After Width: | Height: | Size: 14 KiB |
107
docs/reStructuredText/figures/can-protocol.csv
Normal file
@@ -0,0 +1,107 @@
|
||||
CMD ID,Name,Sender,Signals,Start byte,Signal Type,Bits,Factor,Offset
|
||||
0x000,CANOpen NMT Message**,Master,-,-,-,-,-,-
|
||||
0x001,ODrive Heartbeat Message,Axis,"Axis Error
|
||||
Axis Current State
|
||||
Controller Status","0
|
||||
4
|
||||
7","Unsigned Int
|
||||
Unsigned Int
|
||||
Bitfield","32
|
||||
8
|
||||
8","-
|
||||
-
|
||||
-","-
|
||||
-
|
||||
-"
|
||||
0x002,ODrive Estop Message,Master,-,-,-,-,-,-
|
||||
0x003,Get Motor Error*,Axis,Motor Error,0,Unsigned Int,64,1,0
|
||||
0x004,Get Encoder Error*,Axis,Encoder Error,0,Unsigned Int,32,1,0
|
||||
0x005,Get Sensorless Error*,Axis,Sensorless Error,0,Unsigned Int,32,1,0
|
||||
0x006,Set Axis Node ID,Master,Axis CAN Node ID,0,Unsigned Int,32,1,0
|
||||
0x007,Set Axis Requested State,Master,Axis Requested State,0,Unsigned Int,32,1,0
|
||||
0x008,Set Axis Startup Config,Master,- Not yet implemented -,-,-,-,-,-
|
||||
0x009,Get Encoder Estimates*,Master,"Encoder Pos Estimate
|
||||
Encoder Vel Estimate","0
|
||||
4","IEEE 754 Float
|
||||
IEEE 754 Float","32
|
||||
32","1
|
||||
1","0
|
||||
0"
|
||||
0x00A,Get Encoder Count*,Master,"Encoder Shadow Count
|
||||
Encoder Count in CPR","0
|
||||
4","Signed Int
|
||||
Signed Int","32
|
||||
32","1
|
||||
1","0
|
||||
0"
|
||||
0x00B,Set Controller Modes,Master,"Control Mode
|
||||
Input Mode","0
|
||||
4","Signed Int
|
||||
Signed Int","32
|
||||
32","1
|
||||
1","0
|
||||
0"
|
||||
0x00C,Set Input Pos,Master,"Input Pos
|
||||
Vel FF
|
||||
Torque FF","0
|
||||
4
|
||||
6","IEEE 754 Float
|
||||
Signed Int
|
||||
Signed Int","32
|
||||
16
|
||||
16","1
|
||||
0.001
|
||||
0.001","0
|
||||
0
|
||||
0"
|
||||
0x00D,Set Input Vel,Master,"Input Vel
|
||||
Torque FF","0
|
||||
4","IEEE 754 Float
|
||||
IEEE 754 Float","32
|
||||
32","1
|
||||
1","0
|
||||
0"
|
||||
0x00E,Set Input Torque,Master,Input Torque,0,IEEE 754 Float,32,1,0
|
||||
0x00F,Set Limits,Master,"Velocity Limit
|
||||
Current Limit","0
|
||||
4","IEEE 754 Float
|
||||
IEEE 754 Float",32,"1
|
||||
1","0
|
||||
0"
|
||||
0x010,Start Anticogging,Master,-,-,-,-,-,-
|
||||
0x011,Set Traj Vel Limit,Master,Traj Vel Limit,0,IEEE 754 Float,32,1,0
|
||||
0x012,Set Traj Accel Limits,Master,"Traj Accel Limit
|
||||
Traj Decel Limit","0
|
||||
4","IEEE 754 Float
|
||||
IEEE 754 Float","32
|
||||
32","1
|
||||
1","0
|
||||
0"
|
||||
0x013,Set Traj Inertia,Master,Traj Inertia,0,IEEE 754 Float,32,1,0
|
||||
0x014,Get IQ*,Axis,"Iq Setpoint
|
||||
Iq Measured","0
|
||||
4","IEEE 754 Float
|
||||
IEEE 754 Float","32
|
||||
32","1
|
||||
1","0
|
||||
0"
|
||||
0x015,Get Sensorless Estimates*,Master,"Sensorless Pos Estimate
|
||||
Sensorless Vel Estimate","0
|
||||
4","IEEE 754 Float
|
||||
IEEE 754 Float","32
|
||||
32","1
|
||||
1","0
|
||||
0"
|
||||
0x016,Reboot ODrive,Master***,-,-,-,-,-,-
|
||||
0x017,Get Vbus Voltage,Master***,Vbus Voltage,0,IEEE 754 Float,32,1,0
|
||||
0x018,Clear Errors,Master,-,-,-,-,-,-
|
||||
0x019,Set Linear Count,Master,Position,0,Signed Int,32,1,0
|
||||
0x01A,Set Position Gain,Master,Pos Gain,0,IEEE 754 Float,32,1,0
|
||||
0x01B,Set Vel Gains,Master,"Vel Gain
|
||||
Vel Integrator Gain","0
|
||||
4","IEEE 754 Float
|
||||
IEEE 754 Float","32
|
||||
32","1
|
||||
1","0
|
||||
0"
|
||||
0x700,CANOpen Heartbeat Message**,Slave,-,-,-,-,-,-
|
||||
|
BIN
docs/reStructuredText/figures/controller_with_ff.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
docs/reStructuredText/figures/endstop_figure.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
docs/reStructuredText/figures/ground_loop_bad.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/reStructuredText/figures/ground_loop_fix.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/reStructuredText/figures/liveplotter-iq-omega.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
docs/reStructuredText/figures/liveplotter-pos-estimate.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
docs/reStructuredText/figures/mech_dimensions.png
Normal file
|
After Width: | Height: | Size: 459 KiB |
18
docs/reStructuredText/figures/pinout.csv
Normal file
@@ -0,0 +1,18 @@
|
||||
#,Label,GPIO_MODE_DIGITAL,GPIO_MODE_ANALOG_IN,GPIO_MODE_UART_A,GPIO_MODE_UART_B,GPIO_MODE_PWM,GPIO_MODE_CAN_A,GPIO_MODE_I2C_A,GPIO_MODE_ENC0,GPIO_MODE_ENC1,GPIO_MODE_MECH_BRAKE
|
||||
0,not a pin,,,,,,,,,,
|
||||
1,GPIO1 (+),general purpose,analog input,UART_A.TX,,PWM0.0,,,,,mechanical brake
|
||||
2,GPIO2 (+),general purpose,analog input,UART_A.RX,,PWM0.1,,,,,mechanical brake
|
||||
3,GPIO3,general purpose,analog input,,UART_B.TX,PWM0.2,,,,,mechanical brake
|
||||
4,GPIO4,general purpose,analog input,,UART_B.RX,PWM0.3,,,,,mechanical brake
|
||||
5,GPIO5,general purpose,analog input (*),,,,,,,,mechanical brake
|
||||
6,GPIO6 (*) (+),general purpose,,,,,,,,,mechanical brake
|
||||
7,GPIO7 (*) (+),general purpose,,,,,,,,,mechanical brake
|
||||
8,GPIO8 (*) (+),general purpose,,,,,,,,,mechanical brake
|
||||
9,M0.A,general purpose,,,,,,,ENC0.A,,
|
||||
10,M0.B,general purpose,,,,,,,ENC0.B,,
|
||||
11,M0.Z,general purpose,,,,,,,,,
|
||||
12,M1.A,general purpose,,,,,,I2C.SCL,,ENC1.A,
|
||||
13,M1.B,general purpose,,,,,,I2C.SDA,,ENC1.B,
|
||||
14,M1.Z,general purpose,,,,,,,,,
|
||||
15,not exposed,general purpose,,,,,CAN_A.RX,I2C.SCL,,,
|
||||
16,not exposed,general purpose,,,,,CAN_A.TX,I2C.SDA,,,
|
||||
|
BIN
docs/reStructuredText/figures/secondOrderResponse.PNG
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
docs/reStructuredText/figures/stlink-wiring.jpg
Normal file
|
After Width: | Height: | Size: 997 KiB |
BIN
docs/reStructuredText/figures/thermistor-voltage-divider.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
553
docs/reStructuredText/getting-started.rst
Normal file
74
docs/reStructuredText/ground-loops.rst
Normal file
@@ -0,0 +1,74 @@
|
||||
|
||||
.. _ground-loops:
|
||||
|
||||
================================================================================
|
||||
Ground Loops
|
||||
================================================================================
|
||||
|
||||
For electrical devices to communicate, most of the time they require a common ground connection.
|
||||
Best practice is to connect the grounds back to a single point, called a "star ground".
|
||||
If there are multiple paths to ground, a "ground loop" is formed.
|
||||
Ground loops and wire inductance can cause issues for high current electronics like ODrive.
|
||||
As an example of what can go wrong, look at the diagram below.
|
||||
|
||||
The Problem
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.. image:: figures/ground_loop_bad.png
|
||||
:scale: 80 %
|
||||
:align: center
|
||||
:alt: Ground Loop with inductance
|
||||
|
||||
The issue is the inductance of the power wires between the ODrive and power supply.
|
||||
The inductance and the high current drawn by the ODrive causes V_1 to not be the same as V_2.
|
||||
If the voltage caused by the wire inductance and current is high enough, the 0-5V gpio signals can swing much higher or lower than the normal 0-5V range.
|
||||
This causes a current to flow through the ODrive GPIO pins.
|
||||
|
||||
Solutions
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Reduce Power Wire
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
All wires have some amount of inductance. The inductance is proportional to the length of the wires and the area of the loop formed by the positive and negative power wires.
|
||||
It is beneficial to keep those wires as short as possible and as close together as possible. This reduces the effect of the problem but does not eliminate it!
|
||||
|
||||
Isolation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To fix this, the ground loop must be broken. This can be achieved by isolating the power supplies (no common V-) and connecting a signal ground between the RPi and ODrive.
|
||||
An example of this is a single ODrive connected to a battery and a device like a RPi connected to a mains power supply or different battery.
|
||||
If more than one ODrive is in use and they share a power supply, you have a ground loop again.
|
||||
|
||||
The best way to fix this is to isolate the data connection between the RPi and ODrive(s). The diagram below illustrates where the isolator should go.
|
||||
|
||||
|
||||
.. image:: figures/ground_loop_fix.png
|
||||
:scale: 80 %
|
||||
:align: center
|
||||
:alt: Ground Loop fixed by isolator
|
||||
|
||||
By isolating the data connection (whether it is GPIO, USB, or UART), the ground loop is broken.
|
||||
Isolation can be achieved by using a USB isolator or a signal isolator for GPIO connections.
|
||||
|
||||
Here are some examples of USB isolators:
|
||||
|
||||
* `Isolator 1 <https://www.aliexpress.com/item/33016336073.html?spm=a2g0s.9042311.0.0.57ec4c4dDADzZo>`_
|
||||
* `Isolator 2 <https://www.aliexpress.com/item/4000060726013.html?spm=a2g0s.9042311.0.0.57ec4c4dDADzZo>`_
|
||||
|
||||
These are generic devices. If Aliexpress is not an option for you, you can probably find them available in your area from a different vendor.
|
||||
In the US, these types of isolators are available from Amazon and Ebay.
|
||||
|
||||
For GPIO connections, like UART, Step/dir, PWM, etc, you can use signal isolators like the ISO7762F from Texas Instruments.
|
||||
Keep in mind that isolators have a speed limit. Devices like optocouplers might be too slow for UART connections or Step/direction.
|
||||
Check the datasheet!
|
||||
|
||||
Current Limiting
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If isolators are not an option, you can use series resistors to limit the injection current to a safe level. Place a resistor on the recieving side of all connections to or from the ODrive GPIO pins.
|
||||
4.7kOhms is a good value, but anything from 3.3kOhms to 10kOhms should work. Series resistors offer some protection for the ODrive, but the ground loop problem can still cause the GPIOs to be pulled high or low for short periods of time.
|
||||
The ODrive and your other device will most likely be safe but communications might be interrupted.
|
||||
|
||||
As an example, for UART, you would place a resistor close to the RX pin of the ODrive and another one close to the RX pin of the other device (like an Arduino).
|
||||
This allows the driving side, the TX pins, to adequately drive the bus capacitance.
|
||||
295
docs/reStructuredText/hoverboard.rst
Normal file
@@ -0,0 +1,295 @@
|
||||
|
||||
.. _hoverboard-doc:
|
||||
|
||||
================================================================================
|
||||
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 accompanied by some explanation so hopefully you can carry over some of the steps to other setups and configurations.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/ponx_U4xhoM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
|
||||
|
||||
.. [](https://www.youtube.com/watch?v=ponx_U4xhoM) <br> 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 connector (make sure that the motor channel number matches) as follows:
|
||||
|
||||
.. list-table::
|
||||
:widths: 25 25
|
||||
:header-rows: 1
|
||||
|
||||
* - Hall wire
|
||||
- J4 signal
|
||||
* - Red
|
||||
- 5V
|
||||
* - Yellow
|
||||
- A
|
||||
* - Blue
|
||||
- B
|
||||
* - Green
|
||||
- Z
|
||||
* - Black
|
||||
- GND
|
||||
|
||||
|
||||
.. note::
|
||||
In order to be compatible with encoder inputs, the ODrive doesn't have any filtering capacitors on the pins where the hall sensors connect.
|
||||
Therefore to get a reliable hall signal, it is recommended that you add some filter capacitors to these pins.
|
||||
We recommend about 22nF between each signal pin and GND.
|
||||
You can see instructions `here <https://discourse.odriverobotics.com/t/encoder-error-error-illegal-hall-state/1047/7?u=madcowswe>`__.
|
||||
|
||||
|
||||
Hoverboard Motor Configuration
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Standard 6.5 inch hoverboard hub motors have 30 permanent magnet poles, and thus 15 pole pairs.
|
||||
If you have a different motor you need to count the magnets or have a reliable datasheet for this information.
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.axis0.motor.config.pole_pairs = 15
|
||||
|
||||
|
||||
|
||||
Hoverboard hub motors are quite high resistance compared to the hobby aircraft motors, so we want to use a bit higher voltage for the motor calibration, and set up the current sense gain to be more sensitive.
|
||||
The motors are also fairly high inductance, so we need to reduce the bandwidth of the current controller from the default to keep it stable.
|
||||
The KV rating of the motor also should be known. It can be measured using the "drill test", detailed `here <https://discourse.odriverobotics.com/t/project-hoverarm/441/2?u=madcowswe>`__.
|
||||
If you can't perform this test, a typical value is 16.
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.axis0.motor.config.resistance_calib_max_voltage = 4
|
||||
odrv0.axis0.motor.config.requested_current_range = 25 #Requires config save and reboot
|
||||
odrv0.axis0.motor.config.current_control_bandwidth = 100
|
||||
odrv0.axis0.motor.config.torque_constant = 8.27 / <measured KV>
|
||||
|
||||
|
||||
If you set the encoder to hall mode (instead of incremental).
|
||||
See the :ref:`pinout <encoders-hall-effect>` for instructions on how to plug in the hall feedback.
|
||||
The hall feedback has 6 states for every pole pair in the motor.
|
||||
Since we have 15 pole pairs, we set the cpr to `15*6 = 90`.
|
||||
Since hall sensors are low resolution feedback, we also bump up the offset calibration displacement to get better calibration accuracy.
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.axis0.encoder.config.mode = ENCODER_MODE_HALL
|
||||
odrv0.axis0.encoder.config.cpr = 90
|
||||
odrv0.axis0.encoder.config.calib_scan_distance = 150
|
||||
odrv0.config.gpio9_mode = GPIO_MODE_DIGITAL
|
||||
odrv0.config.gpio10_mode = GPIO_MODE_DIGITAL
|
||||
odrv0.config.gpio11_mode = GPIO_MODE_DIGITAL
|
||||
|
||||
|
||||
Since the hall feedback only has 90 counts per revolution, we want to reduce the velocity tracking bandwidth to get smoother velocity estimates.
|
||||
We can also set these fairly modest gains that will be a bit sloppy but shouldn't shake your rig apart if it's built poorly.
|
||||
Make sure to tune the gains up when you have everything else working to a stiffness that is applicable to your application.
|
||||
Lets also start in velocity control mode since that is probably what you want for a wheeled robot. Note that in velocity mode :code:`pos_gain` isn't used but I have given you a recommended value anyway in case you wanted to run position control mode.
|
||||
|
||||
.. note::
|
||||
The gains used here are dependent on the :code:`torque_constant` and :code:`cpr` config settings. The values for hoverboard motors are **very different** from the stock settings. Do not skip the above steps and go straight to these settings!
|
||||
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.axis0.encoder.config.bandwidth = 100
|
||||
odrv0.axis0.controller.config.pos_gain = 1
|
||||
odrv0.axis0.controller.config.vel_gain = 0.02 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr
|
||||
odrv0.axis0.controller.config.vel_integrator_gain = 0.1 * odrv0.axis0.motor.config.torque_constant * odrv0.axis0.encoder.config.cpr
|
||||
odrv0.axis0.controller.config.vel_limit = 10
|
||||
odrv0.axis0.controller.config.control_mode = CONTROL_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 require a reboot are applied first.
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.save_configuration()
|
||||
odrv0.reboot()
|
||||
|
||||
|
||||
Make sure the motor is free to move, then activate the motor calibration.
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.axis0.requested_state = AXIS_STATE_MOTOR_CALIBRATION
|
||||
|
||||
|
||||
You can read out all the data pertaining to the motor:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.axis0.motor
|
||||
|
||||
Check to see that there is no error and that the phase resistance and inductance are reasonable. Here are the results I got:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
error = 0x0000 (int)
|
||||
phase_inductance = 0.00033594953129068017 (float)
|
||||
phase_resistance = 0.1793474406003952 (float)
|
||||
|
||||
|
||||
If all looks good then you can tell the ODrive that saving this calibration to persistent memory is OK:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.axis0.motor.config.pre_calibrated = True
|
||||
|
||||
|
||||
Next step is to check the alignment between the motor and the hall sensor.
|
||||
Because of this step you are allowed to plug the motor phases in random order and also the hall signals can be random.
|
||||
Just don't change it after calibration.
|
||||
|
||||
Make sure the motor is free to move and run:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.axis0.requested_state = AXIS_STATE_ENCODER_HALL_POLARITY_CALIBRATION
|
||||
|
||||
|
||||
Check the status of the encoder object:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.axis0.encoder
|
||||
|
||||
|
||||
Check that there are no errors.
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
error = 0x0000 (int)
|
||||
|
||||
|
||||
If the hall encoder polarity calibration was successful, run the encoder offset calibration.
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.axis0.requested_state = AXIS_STATE_ENCODER_OFFSET_CALIBRATION
|
||||
|
||||
|
||||
Check the status of the encoder object:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.axis0.encoder
|
||||
|
||||
|
||||
Check that there are no errors.
|
||||
If your hall sensors has a standard timing angle then :code:`phase_offset_float` should be close to 0.5 mod 1. Meaning values close to -1.5, -0.5, 0.5, or 1.5, etc are all good.
|
||||
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
error = 0x0000 (int)
|
||||
config:
|
||||
phase_offset_float = 0.5126956701278687 (float)
|
||||
|
||||
|
||||
If all looks good then you can tell the ODrive that saving this calibration to presistent memory is OK:
|
||||
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.axis0.encoder.config.pre_calibrated = True
|
||||
|
||||
|
||||
OK, we are now done with the motor configuration! Time to save, reboot, and then test it.
|
||||
The ODrive starts in idle (we will look at changing this later) so we can enable closed loop control.
|
||||
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.save_configuration()
|
||||
odrv0.reboot()
|
||||
odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
|
||||
odrv0.axis0.controller.input_vel = 2
|
||||
# Your motor should spin here
|
||||
odrv0.axis0.controller.input_vel = 0
|
||||
odrv0.axis0.requested_state = AXIS_STATE_IDLE
|
||||
|
||||
|
||||
Hopefully you got your motor to spin! Feel free to repeat all of the above for the other axis if appropriate.
|
||||
|
||||
PWM Input
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
If you want to drive your hoverboard wheels around with an RC remote control you can use the :ref: `RC PWM input <rc-pwm>`. There is more information in that link.
|
||||
Lets use GPIO 3/4 for the velocity inputs so that we don't have to disable UART.
|
||||
Then let's map the full stick range of these inputs to some suitable velocity setpoint range.
|
||||
We also have to reboot to activate the PWM input.
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.config.gpio3_pwm_mapping.min = -2
|
||||
odrv0.config.gpio3_pwm_mapping.max = 2
|
||||
odrv0.config.gpio3_pwm_mapping.endpoint = odrv0.axis0.controller._input_vel_property
|
||||
|
||||
odrv0.config.gpio4_pwm_mapping.min = -2
|
||||
odrv0.config.gpio4_pwm_mapping.max = 2
|
||||
odrv0.config.gpio4_pwm_mapping.endpoint = odrv0.axis1.controller._input_vel_property
|
||||
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.save_configuration()
|
||||
odrv0.reboot()
|
||||
|
||||
|
||||
Now we can check that the sticks are writing to the velocity setpoint.
|
||||
Move the stick, print :code:`input_vel`, move to a different position, check again.
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
In [1]: odrv0.axis1.controller.input_vel
|
||||
Out[1]: 0.01904754638671875
|
||||
|
||||
In [2]: odrv0.axis1.controller.input_vel
|
||||
Out[2]: 0.01904754638671875
|
||||
|
||||
In [3]: odrv0.axis1.controller.input_vel
|
||||
Out[3]: 1.152389526367188
|
||||
|
||||
In [4]: odrv0.axis1.controller.input_vel
|
||||
Out[4]: 1.81905517578125
|
||||
|
||||
In [5]: odrv0.axis1.controller.input_vel
|
||||
Out[5]: -0.990474700927734
|
||||
|
||||
Ok, now we should be able to turn on the drive and control the wheels!
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
|
||||
odrv0.axis1.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL
|
||||
|
||||
|
||||
Safety
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Be sure to setup the Failsafe feature on your RC Receiver so that if connection is lost between the remote and the receiver, the receiver outputs 0 and 0 for the velocity setpoint of both axes (or whatever is safest for your configuration). Also note that if the receiver turns off (loss of power, etc) or if the signal from the receiver to the ODrive is lost (wire comes unplugged, etc), the ODrive will continue the last commanded velocity setpoint. There is currently no timeout function in the ODrive for PWM inputs.
|
||||
|
||||
Automatic Startup
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Try to reboot and then activate :code:`AXIS_STATE_CLOSED_LOOP_CONTROL` on both axis.
|
||||
Check that everything is operational and works as expected.
|
||||
If so, you can now make the ODrive turn on the motor power automatically after booting.
|
||||
This is useful if you are going to be running the ODrive without a PC or other logic board.
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrv0.axis0.config.startup_closed_loop_control = True
|
||||
odrv0.axis1.config.startup_closed_loop_control = True
|
||||
odrv0.save_configuration()
|
||||
odrv0.reboot()
|
||||
|
||||
65
docs/reStructuredText/index.rst
Normal file
@@ -0,0 +1,65 @@
|
||||
.. ODrive Documentation documentation master file, created by
|
||||
sphinx-quickstart on Wed Nov 3 20:01:31 2021.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to ODrive Documentation's documentation!
|
||||
================================================
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
:caption: General
|
||||
|
||||
getting-started
|
||||
odrivetool
|
||||
control-modes
|
||||
commands
|
||||
encoders
|
||||
control
|
||||
troubleshooting
|
||||
specifications
|
||||
ground-loops
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
:caption: Tutorials
|
||||
|
||||
Hoverboard Guide <hoverboard>
|
||||
migration
|
||||
CAN Guide <can-guide>
|
||||
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
:caption: Interfaces and Protocols
|
||||
|
||||
protocol
|
||||
Pinout <pinout>
|
||||
usb
|
||||
uart
|
||||
native-protocol
|
||||
ascii-protocol
|
||||
can-protocol
|
||||
Step & Direction <step-direction>
|
||||
rc-pwm
|
||||
analog-input
|
||||
endstops
|
||||
thermistors
|
||||
|
||||
|
||||
.. fibreautosummary:: com.odriverobotics.ODrive
|
||||
:caption: ODrive Device API
|
||||
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:maxdepth: 1
|
||||
:caption: For ODrive Developers
|
||||
|
||||
developer-guide
|
||||
configuring-vscode
|
||||
configuring-eclipse
|
||||
|
||||
35
docs/reStructuredText/make.bat
Normal file
@@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
122
docs/reStructuredText/migration.rst
Normal file
@@ -0,0 +1,122 @@
|
||||
================================================================================
|
||||
Migration Guide
|
||||
================================================================================
|
||||
|
||||
v0.5.1 -> v0.5.2
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
The change from v0.5.1 to v0.5.2 had fewer breaking changes than the v0.4.12 to v0.5.1 change.
|
||||
|
||||
GPIO Modes
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
The GPIO configuration is now more explicit. For example, to use :code:`gpio1` for for step signals (as part of a step/dir interface), it must be set to
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.config.gpio1_mode = GPIO_MODE_DIGITAL
|
||||
|
||||
|
||||
Braking Behavior
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Before using the brake resistor, it must be explicitly enabled as follows:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.config.enable_brake_resistor = True
|
||||
|
||||
and then save the configuration and reboot for the setting to take effect.
|
||||
|
||||
Step/Direction Settings
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Previously, steps were added incrementally to :code:`input_pos`.
|
||||
This caused issues with accumulated floating point rounding error.
|
||||
Now, an absolute step count is used.
|
||||
This change requires that the circular setpoints mode is used :code:`odrv0.axis0.controller.config.circular_setpoints = True` when step/dir signals are used.
|
||||
|
||||
In addition, :code:`odrv0.axis0.config.turns_per_step` has been removed and :code:`odrv0.axis0.controller.config.steps_per_circular_range` is used.
|
||||
|
||||
For example:
|
||||
|
||||
previously:
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.config.turns_per_step = 1.0/1024.0
|
||||
|
||||
v0.5.2
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.axis0.controller.config.circular_setpoints = True
|
||||
odrv0.axis0.controller.config.circular_setpoint_range = 1.0
|
||||
odrv0.axis0.controller.config.steps_per_circular_range = 1024
|
||||
|
||||
For best results, set both the circular range and steps per circular range to powers of 2.
|
||||
|
||||
API changes
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
For other API changes, see the Changelog file on github.
|
||||
|
||||
v0.4.12 -> v0.5.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Certain changes occurred between firmware versions v0.4.12 and v0.5.1 that will break existing configurations.
|
||||
This document is a guide for how to take a working v0.4.12 ODrive config and change it to work with firmware v0.5.1.
|
||||
|
||||
Unit Changes
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
ODrive now uses units of [turns], [turns/s], and [turns/s^2] instead of [counts], [counts/s], and [counts/s^2].
|
||||
In addition, the motor controller class now has an input command of torque in [Nm] instead of current in [Amps].
|
||||
In general, every user-facing parameter that has to do with position or velocity is affected by the unit change.
|
||||
|
||||
.. note::
|
||||
For the torque to be in correct in [Nm] you need to configure the :code:`motor.config.torque_constant`.
|
||||
See the updated :ref:`getting started <motor-config>` for more details.
|
||||
|
||||
Control Parameter Names
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
ODrive now uses :code:`input_pos`, :code:`input_vel`, and :code:`input_torque` as commands instead of :code:`pos_setpoint`, :code:`vel_setpoint`, and :code:`current_setpoint`.
|
||||
|
||||
Guide
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
For a working v0.4.12 ODrive configuration, use the following equations to convert parameters as required.
|
||||
|
||||
* :code:`pos_gain` is unaffected ( [counts/s / count] `*`> [turns/s / turns] )
|
||||
* :code:`vel_gain` is :code:`vel_gain_old * torque_constant * encoder cpr`
|
||||
* :code:`vel_integrator_gain` is :code:`vel_integrator_gain_old * torque_constant * encoder cpr`
|
||||
|
||||
For other values, [turns] = [counts] / [encoder cpr]. Converting [counts/s] and [counts/s^2] is similar.
|
||||
|
||||
Affected Variables
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* :code:`axis.controller.input_pos`
|
||||
* :code:`axis.controller.input_vel`
|
||||
* :code:`axis.controller.input_torque`
|
||||
* :code:`axis.controller.config.vel_limit`
|
||||
* :code:`axis.controller.config.vel_ramp_rate`
|
||||
* :code:`axis.controller.config.current_ramp_rate` is now :code:`axis.controller.config.torque_ramp_rate`
|
||||
* :code:`axis.controller.config.circular_setpoint_range`
|
||||
* :code:`axis.controller.config.inertia`
|
||||
* :code:`axis.controller.config.homing_speed`
|
||||
* :code:`axis.controller.pos_setpoint`
|
||||
* :code:`axis.controller.vel_setpoint`
|
||||
* :code:`axis.controller.torque_setpoint` instead of :code:`axis.controller.current_setpoint`
|
||||
* :code:`axis.trap_traj.config.vel_limit`
|
||||
* :code:`axis.trap_traj.config.accel_limit`
|
||||
* :code:`axis.trap_traj.config.decel_limit`
|
||||
* :code:`axis.encoder.pos_estimate`
|
||||
* :code:`axis.encoder.pos_estimate_circular`
|
||||
* :code:`axis.encoder.vel_estimate`
|
||||
* :code:`axis.config.counts_per_step` is now :code:`turns_per_step` for the step/direction interface
|
||||
* :code:`axis.sensorless_estimator.vel_estimate` is in mechanical [turns/s] instead of electrical [radians/s]
|
||||
|
||||
32
docs/reStructuredText/native-protocol.rst
Normal file
@@ -0,0 +1,32 @@
|
||||
.. _native-protocol:
|
||||
|
||||
================================================================================
|
||||
Native Protocol
|
||||
================================================================================
|
||||
|
||||
This protocol is what the :code:`odrivetool` uses to talk to the ODrive.
|
||||
If you have a choice, this is the recommended protocol for all applications.
|
||||
The native protocol runs on :ref:`USB <usb-doc>` and on :ref:`UART <uart-doc>`.
|
||||
|
||||
Python
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
The :code:`odrivetool` you installed as part of the :ref:`Getting Started guide <install-odrivetool>` comes with a library that you can use to easily control the ODrive from Python.
|
||||
|
||||
Assuming you already installed the odrive library (:code:`pip install odrive`), the simplest program to control the ODrive is this:
|
||||
|
||||
.. code:: Python
|
||||
|
||||
import odrive
|
||||
odrv0 = odrive.find_any()
|
||||
print(str(odrv0.vbus_voltage))
|
||||
|
||||
|
||||
For a more comprehensive example, see :ref:`tools/odrive_demo.py <../tools/odrive_demo.py>`.
|
||||
|
||||
Other Languages
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
We don't have an official library for you just yet. Check the community, there might be someone working on it.
|
||||
If you want to write a library yourself, refer to the :ref:`native protocol specification <protocol-doc>`.
|
||||
You are of course welcome to contribute it back.
|
||||
391
docs/reStructuredText/odrivetool.rst
Normal file
@@ -0,0 +1,391 @@
|
||||
.. _odrivetool-doc:
|
||||
|
||||
================================================================================
|
||||
:code:`odrivetool`
|
||||
================================================================================
|
||||
|
||||
The :code:`odrivetool` is the accompanying PC program for the ODrive. It's main purpose is to provide an interactive shell to control the device manually, as well as some supporting functions like firmware update.
|
||||
|
||||
Installation
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Refer to the :ref:`Getting Started guide <install-odrivetool>`.
|
||||
|
||||
Type :code:`odrivetool --help` to see what features are available.
|
||||
|
||||
Multiple ODrives
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
By default, :code:`odrivetool` will connect to any ODrive it finds.
|
||||
If this is not what you want, you can select a specific ODrive.
|
||||
|
||||
To find the serial number of your ODrive, run :code:`odrivetool`, connect exactly one ODrive and power it up.
|
||||
You should see this:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
Connected to ODrive 306A396A3235 as odrv0
|
||||
In [1]:
|
||||
|
||||
:code:`306A396A3235` is the serial number of this particular ODrive.
|
||||
If you want :code:`odrivetool` to ignore all other devices you would close it and then run:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrivetool --serial-number 306A396A3235
|
||||
|
||||
.. dropdown:: My ODrive is stuck in DFU mode, can I still find the serial number?
|
||||
|
||||
Yes, the serial number is part of the USB descriptors.
|
||||
In Linux you can find it by running:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
(sudo lsusb -d 1209:0d32 -v; sudo lsusb -d 0483:df11 -v) 2>/dev/null | grep iSerial
|
||||
|
||||
This should output something like:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
iSerial 3 385F324D3037
|
||||
iSerial 3 306A396A3235
|
||||
|
||||
Here, two ODrives are connected.
|
||||
|
||||
Configuration Backup
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
You can use :code:`odrivetool` to back up and restore device configurations or transfer the configuration of one ODrive to another one.
|
||||
|
||||
* To save the configuration to a file on the PC, run
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrivetool backup-config my_config.json
|
||||
|
||||
* To restore the configuration form such a file, run
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrivetool restore-config my_config.json
|
||||
|
||||
.. note::
|
||||
|
||||
The encoder offset calibration is not restored because this would be dangerous if you transfer the calibration values of one axis to another axis.
|
||||
|
||||
.. _firmware-update:
|
||||
|
||||
Device Firmware Update
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
.. attention::
|
||||
|
||||
DFU is not supported on ODrive v3.4 or earlier. You need to :ref:`flash with the external programmer <flashing-with-an-stlink>` instead.
|
||||
|
||||
To update the ODrive to the newest firmware release, simply open up a terminal and run the following command:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrivetool dfu
|
||||
|
||||
You should then see
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
ODrive control utility v0.3.7.dev
|
||||
Waiting for ODrive...
|
||||
Found ODrive 308039673235 (v3.5-24V) with firmware v0.3.7-dev
|
||||
Checking online for newest firmware... found v0.3.7
|
||||
Downloading firmware...
|
||||
Putting device 308039673235 into DFU mode...
|
||||
Erasing... done
|
||||
Flashing... done
|
||||
Verifying... done
|
||||
|
||||
|
||||
.. note:: This command will connect to GitHub servers to retrieve the latest firmware.
|
||||
|
||||
If you have a non-default configuration saved on the device, :code:`odrivetool` will try to carry over the configuration across the firmware update.
|
||||
If any of the settings are removed or renamed, you will get warning messages.
|
||||
|
||||
Flashing Custom Firmware
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you want to flash a specific firmware file instead of automatically downloading one, you can run
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
odrivetool dfu path/to/firmware/file.hex
|
||||
|
||||
You can download one of the officially released firmware files from `here <https://github.com/madcowswe/ODrive/releases>`__.
|
||||
.. You will need one of the :code:`.hex` files (not the :code:`.elf`file).
|
||||
On Windows you will need one of the :code:`.hex` files, and for Linux and Mac you will want the :code:`.elf` file.
|
||||
Make sure you select the file that matches your board version.
|
||||
|
||||
To compile firmware from source, refer to the :ref:`developer guide <developer-guide-doc>`.
|
||||
|
||||
Troubleshooting
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.. tabs::
|
||||
.. tab:: **Windows**
|
||||
|
||||
During the update, a new device called "STM32 BOOTLOADER" will appear.
|
||||
Open the `Zadig utility <http://zadig.akeo.ie/>`_ and set the driver for "STM32 BOOTLOADER" to libusb-win32.
|
||||
After that the firmware update will continue.
|
||||
|
||||
.. tab:: **Linux**
|
||||
|
||||
Try running :code:`sudo odrivetool dfu` instead of :code:`odrivetool dfu`.
|
||||
|
||||
On some machines you will need to unplug and plug back in the USB cable to make the PC understand that we switched from regular mode to bootloader mode.
|
||||
|
||||
.. _force-dfu:
|
||||
|
||||
**Forcing DFU Mode**
|
||||
|
||||
If the DFU script can't find the device, try forcing it into DFU mode:
|
||||
|
||||
.. tabs::
|
||||
.. tab:: ODrive v3.5 and newer
|
||||
|
||||
Flick the DIP switch that says "DFU, RUN" to "DFU" and power cycle the board.
|
||||
If that alone doesn't work, also connect the pin "GPIO6" to "GND".
|
||||
After you're done upgrading firmware, don't forget to put the switch back into the "RUN" position and power cycle the board again.
|
||||
|
||||
.. tab:: ODrive v3.1, v3.2
|
||||
|
||||
Connect the pin "BOOT0" to "3.3V" and power cycle the board.
|
||||
If that alone doesn't work, also connect the pin "GPIO1" to "GND".
|
||||
After you're done, remove the wires and power cycle the board again.
|
||||
|
||||
|
||||
.. _st-link-flash:
|
||||
|
||||
Alternative DFU Tool
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Some people have had issues using the python dfu tool, so below is a guide on how to manually use different tools.
|
||||
|
||||
Before starting the below steps, you need to get firmware binary.
|
||||
You can download one of the officially released firmware files from `here <https://github.com/madcowswe/ODrive/releases/latest>`__.
|
||||
Make sure you select the file that matches your board version.
|
||||
On Windows you will need one of the :code:`.hex` files, and for Linux and Mac you will want the :code:`.elf` file.
|
||||
|
||||
To compile firmware from source, refer to the :ref:`developer guide <developer-guide-doc>`.
|
||||
|
||||
**Multi-platform**
|
||||
|
||||
ST has a tool called STM32CubeProgrammer.
|
||||
|
||||
#. Download the tool `here <https://www.st.com/en/development-tools/stm32cubeprog.html>`__.
|
||||
You will need to make an account with ST to download the tool.
|
||||
#. Install the tool. On Windows, make sure to let it make a desktop shortcut.
|
||||
#. Force the ODrive into :ref:`DFU mode. <force-dfu>`
|
||||
#. Launch STM32CubeProgrammer.
|
||||
#. Under "Memory & File edition", there are two tabs called "Device memory" and "Open file".
|
||||
Click "Open file" and choose the ODrive firmware hex file that you downloaded or compiled.
|
||||
#. In the top right, there is a dropdown menu containing the different methods to connect to an STM32 device. Choose "USB".
|
||||
#. Under "USB configuration", a USB port should be automatically selected and the ODrive serial number should be present next to "Serial number."
|
||||
#. Click "Connect" above "USB configuration".
|
||||
#. Click the tab with the name of your firmware file (example: :code:`ODriveFirmware_v3.6-56V.hex`) if it is not already selected.
|
||||
#. Click "Download" to flash your ODrive with the firmware. Your ODrive is now flashed!
|
||||
#. Close STM32CubeProgrammer.
|
||||
#. Turn off the power to the ODrive and set the DIP swtich back to RUN mode.
|
||||
|
||||
.. tabs::
|
||||
.. tab:: **Windows**
|
||||
|
||||
You can use the DfuSe app from ST.
|
||||
|
||||
#. Download the tool `here <https://www.st.com/en/development-tools/stsw-stm32080.html>`__.
|
||||
Unfortunately they make you create a login to download. Sorry about that.
|
||||
#. After installing the tool, launch :code:`DfuFileMgr.exe` which probably got added to the start menu as "Dfu file manager".
|
||||
#. Select "I want to GENERATE a DFU file from S19, HEX or BIN files", press :kbd:`OK`.
|
||||
#. Click the button that says "S19 or Hex...", find the :code:`ODriveFirmware.hex` file you built or downloaded.
|
||||
#. Leave all the other settings as default and click the "Generate..." button.
|
||||
#. Save the output file as :code:`ODriveFirmware.dfu`. Note that the success message has a warning sign for some reason...
|
||||
#. Launch :code:`DfuSeDemo.exe` which probably got added to the start menu as "DfuSeDemo".
|
||||
#. Force the ODrive into DFU mode, as per the instructions above "How to force DFU mode".
|
||||
#. In the top left it should now be connected to "STM Device in DFU Mode".
|
||||
#. If it doesn't appear, it may be because the driver is set to libusb by Zadig. We need to set it back to the original driver.
|
||||
Follow `these instructions <https://github.com/pbatard/libwdi/wiki/FAQ#Help_Zadig_replaced_the_driver_for_the_wrong_device_How_do_I_restore_it>`_.
|
||||
#. If, after doing the above step, the ODrive still installs itself as a libusb device in Device Manager, you can try to delete the libusb driver (this is OK, since we can use Zadig to install it again).
|
||||
You can simply delete the file :code:`C:\Windows\System32\drivers\libusb0.sys`.
|
||||
#. In the bottom right section called "Upgrade or Verify Action" click the button "Choose...".
|
||||
#. Locate the :code:`ODriveFirmware.dfu` we made before.
|
||||
#. Click button "Upgrade".
|
||||
#. If you get a warning that it's not possible to check that it's the correct device type: click yes to continue.
|
||||
#. Congratulations your ODrive should now be flashed; you can now quit DfuSeDemo.
|
||||
#. Turn off the power to the ODrive and set the DIP switch back to RUN mode.
|
||||
|
||||
.. tab:: **Linux**
|
||||
|
||||
Install :code:`dfu-util`
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo apt install dfu-util
|
||||
|
||||
:ref:`Force DFU mode. <force-dfu>`
|
||||
|
||||
In the Firmware directory, after finishing building the firmware run:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo dfu-util -a 0 -s 0x08000000 -D build/ODriveFirmware.bin
|
||||
|
||||
.. tab:: **macOS**
|
||||
|
||||
First, you need to install the arm development tools to copy the binary into the appropriate format:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
brew install --cask gcc-arm-embedded
|
||||
|
||||
Then convert the binary to .bin format:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
arm-none-eabi-objcopy -O binary ODriveFirmware_v3.5-48V.elf ODriveFirmware_v3.5-48V.bin
|
||||
|
||||
Install :code:`dfu-util`
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
brew install dfu-util
|
||||
|
||||
.. note:: If Using MacPorts
|
||||
|
||||
Instead run:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo port install dfu-util
|
||||
|
||||
Put the ODrive into DFU mode using the DIP switch, then turn it on and plug in the USB.
|
||||
Find the correct device serial number using:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
dfu-util --list
|
||||
|
||||
This should return something like:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
Found DFU: [0483:df11] ver=2200, devnum=5, cfg=1, intf=0, path="20-2", alt=0,
|
||||
name="@Internal Flash /0x08000000/04*016Kg,01*064Kg,07*128Kg", serial="388237123123"
|
||||
|
||||
Finally, flash the firmware using the found serial number:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo dfu-util -S 388237123123 -a 0 -s 0x08000000 -D ODriveFirmware_v3.5-48V.bin
|
||||
|
||||
Flashing with an STLink
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
This procedure is only necessary for ODrive v3.4 or earlier. You will need an STLink/v2 or compatible programmer. You should have received one with your ODrive.
|
||||
|
||||
#. Install OpenOCD
|
||||
* **Windows:** `instructions <http://gnuarmeclipse.github.io/openocd/install/>`_ (also follow the instructions on the ST-LINK/V2 drivers)
|
||||
* **Linux:** :code:`sudo apt-get install openocd`
|
||||
* **macOS:** :code:`brew install openocd`
|
||||
#. Download the latest firmware release form `here <https://github.com/madcowswe/ODrive/releases>`__. You will need the :code:`.elf` file.
|
||||
Make sure you select the file that matches your board version.
|
||||
#. Wire up the ODrive and STLink/v2 programmer as shown in this picture
|
||||
|
||||
.. figure:: figures/stlink-wiring.jpg
|
||||
:scale: 18 %
|
||||
|
||||
Stlink Wiring Diagram
|
||||
|
||||
and power up the ODrive.
|
||||
|
||||
#. Open up a terminal and navigate to the directory where the firmware is.
|
||||
#. Run the following command (replace :code:`ODriveFirmware_v3.4-24V.elf` with the name of your firmware file):
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c init -c "reset halt" -c "flash write_image erase ODriveFirmware_v3.4-24V.elf" -c "reset run" -c exit
|
||||
|
||||
If everything worked correctly, you should see something similar to this towards the end of the printout:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
wrote 262144 bytes from file ODriveFirmware_v3.4-24V.elf in 10.194110s (25.113 KiB/s)
|
||||
|
||||
If something doesn't work, make sure :code:`openocd` is in your :code:`PATH` variable, check that the wires are connected properly and try with elevated privileges.
|
||||
|
||||
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
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
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 :code:`closed_loop_control` while they are being forced off position by hand.
|
||||
|
||||
.. figure:: figures/liveplotter-pos-estimate.png
|
||||
:scale: 100 %
|
||||
|
||||
To change what parameters are plotted open odrivetool (located in :code:`Anaconda3\Scripts` or :code:`ODrive-master\tools`) with a text editor and modify the liveplotter function:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
# If you want to plot different values, change them here.
|
||||
# You can plot any number of values concurrently.
|
||||
cancellation_token = start_liveplotter(lambda: [
|
||||
odrv0.axis0.encoder.pos_estimate,
|
||||
odrv0.axis1.encoder.pos_estimate,
|
||||
])
|
||||
|
||||
For example, to plot the approximate motor torque [Nm] and the velocity [RPM] of axis0, you would modify the function to read:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
# If you want to plot different values, change them here.
|
||||
# You can plot any number of values concurrently.
|
||||
cancellation_token = start_liveplotter(lambda: [
|
||||
((odrv0.axis0.encoder.vel_estimate*60), # turns/s to rpm
|
||||
((odrv0.axis0.motor.current_control.Iq_setpoint * my_odrive.axis0.motor.config.torque_constant), # Torque [Nm]
|
||||
])
|
||||
|
||||
In the example below the motor is forced off axis by hand and held there.
|
||||
In response the motor controller increases the torque (orange line) to counteract this disturbance up to a peak of 500 N.cm at which point the motor current limit is reached.
|
||||
When the motor is released it returns back to its commanded position very quickly as can be seen by the spike in the motor velocity (blue line).
|
||||
|
||||
.. figure:: figures/liveplotter-iq-omega.png
|
||||
:scale: 100 %
|
||||
|
||||
Liveplotter Torque Velocity Plot
|
||||
|
||||
To change the scale and sample rate of the plot modify the following parameters located at the beginning of utils.py (located in :code:`Anaconda3\Lib\site-packages\odrive`):
|
||||
|
||||
.. code:: python
|
||||
|
||||
data_rate = 100
|
||||
plot_rate = 10
|
||||
num_samples = 1000
|
||||
|
||||
|
||||
For more examples on how to interact with the plotting functionality refer to these `Matplotlib examples. <https://matplotlib.org/examples>`_
|
||||
|
||||
Liveplotter from Interactive :code:`odrivetool` Instance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can also run :code:`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.
|
||||
|
||||
For example you can type the following directly into the interactive prompt:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
start_liveplotter(lambda: [odrv0.axis0.encoder.pos_estimate])
|
||||
|
||||
Just like the examples above, you can list several parameters to plot separated by comma in the square brackets.
|
||||
In general, you can plot any variable that you are able to read like normal in odrivetool.
|
||||
|
||||
35
docs/reStructuredText/pinout.rst
Normal file
@@ -0,0 +1,35 @@
|
||||
.. _pinout-chart:
|
||||
|
||||
================================================================================
|
||||
ODrive v3.x Pinout
|
||||
================================================================================
|
||||
|
||||
.. ODrive v4.1
|
||||
.. --------------------------------------------------------------------------------
|
||||
|
||||
.. **TODO**
|
||||
|
||||
.. ODrive v3.x
|
||||
.. --------------------------------------------------------------------------------
|
||||
|
||||
.. csv-table:: Table Title
|
||||
:file: figures/pinout.csv
|
||||
:header-rows: 1
|
||||
|
||||
**key:**
|
||||
|
||||
* **(*)** ODrive v3.5 and later.
|
||||
|
||||
* **(+)** On ODrive v3.5 and later these pins have noise suppression filters. This is useful for step/dir input.
|
||||
|
||||
Notes
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Changes to the pin configuration only take effect after :code:`odrv0.save_configuration()` and :code:`odrv0.reboot()`
|
||||
* Bold font marks the default configuration.
|
||||
* If a GPIO is set to an unsupported mode it will be left uninitialized.
|
||||
* When setting a GPIO to a special purpose mode (e.g. :code:`GPIO_MODE_UART_A`) you must also enable the corresponding feature (e.g. :code:`<odrv>.config.enable_uart_a`).
|
||||
* Digital mode is a general purpose mode that can be used for these functions: step, dir, enable, encoder index, hall effect encoder, SPI encoder nCS.
|
||||
* You must also connect GND between ODrive and your other board.
|
||||
* ODrive v3.3 and onward have 5V tolerant GPIO pins.
|
||||
* Simultaneous operation of UART_A and UART_B is currently not supported.
|
||||
106
docs/reStructuredText/protocol.rst
Normal file
@@ -0,0 +1,106 @@
|
||||
.. _protocol-doc:
|
||||
|
||||
================================================================================
|
||||
ODrive Communication Protocol
|
||||
================================================================================
|
||||
|
||||
Communicating with an ODrive consists of a series of endpoint operations.
|
||||
An endpoint can theoretically be any kind data serialized in any way.
|
||||
There is a default serialization implementation for POD types; for custom types
|
||||
you must (de)serialize yourself. In the future we may provide a default serializer
|
||||
for structs.
|
||||
The available endpoints can be enumerated by reading the JSON from endpoint 0
|
||||
and can theoretically be different for each communication interface (they are not in practice).
|
||||
|
||||
Each endpoint operation can send bytes to one endpoint (referenced by its ID)
|
||||
and at the same time receive bytes from the same endpoint. The semantics of
|
||||
these payloads are specific to each endpoint's type, the name of which is
|
||||
indicated in the JSON.
|
||||
|
||||
For instance an int32 endpoint's input and output is a 4 byte little endian
|
||||
representation. In general the convention for combined read/write requests is
|
||||
`exchange`, i.e. the returned value is the old value. Custom endpoint handlers
|
||||
may be non-compliant.
|
||||
|
||||
There is a packet based version and a stream based variant of the protocol. Each
|
||||
variant is employed as appropriate. For instance USB runs the packet based variant
|
||||
by default while UART runs the stream based variant.
|
||||
|
||||
|
||||
Packet Format
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
We will call the ODrive "server" and the PC "client". A request is a message
|
||||
from the PC to the ODrive and a response is a message from the ODrive to the
|
||||
PC.
|
||||
|
||||
Each request-response transaction corresponds to a single endpoint operation.
|
||||
|
||||
**Request**
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* **Bytes 0, 1** Sequence number, MSB = 0
|
||||
* Currently the server does not care about ordering and does not filter resent messages.
|
||||
|
||||
* **Bytes 2, 3** Endpoint ID
|
||||
* The IDs of all endpoints can be obtained from the JSON definition. The JSON definition can be obtained by reading from endpoint 0.
|
||||
If (and only if) the MSB is set to 1 the client expects a response for this request.
|
||||
|
||||
* **Bytes 4, 5** Expected response size
|
||||
* The number of bytes that should be returned to the client. If the client doesn't need any response data, it can set this value to 0. The operation will still be acknowledged if the
|
||||
MSB in EndpointID is set.
|
||||
|
||||
* **Bytes 6 to N-3** Payload
|
||||
* The length of the payload is determined by the total packet size. The format of the payload depends on the endpoint type. The endpoint type can be obtained from the JSON definition.
|
||||
|
||||
* **Bytes N-2, N-1**
|
||||
* For endpoint 0: Protocol version (currently 1). A server shall ignore packets with other values.
|
||||
* For all other endpoints: The CRC16 calculated over the JSON definition using the algorithm described below, except that the initial value is set to the protocol version (currently 1). A server shall ignore packets that set this field incorrectly.
|
||||
|
||||
**Response**
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* **Bytes 0, 1** Sequence number, MSB = 1
|
||||
* The sequence number of the request to which this is the response.
|
||||
|
||||
* **Bytes 2, 3** Payload
|
||||
* The length of the payload tends to be equal to the number of expected bytes as indicated
|
||||
in the request. The server must not expect the client to accept more bytes than it requested.
|
||||
|
||||
Stream Format
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
The stream based format is just a wrapper for the packet format.
|
||||
|
||||
* **Byte 0** Sync byte `0xAA`
|
||||
* **Byte 1** Packet length
|
||||
* Currently both parties shall only emit and accept values of 0 through 127.
|
||||
|
||||
* **Byte 2** CRC8 of bytes 0 and 1 (see below for details)
|
||||
* **Bytes 3 to N-3** Packet
|
||||
* **Bytes N-2, N-1** CRC16 (see below for details)
|
||||
|
||||
CRC Algorithms
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
**CRC8**
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Polynomial: `0x37`
|
||||
* Initial value: `0x42`
|
||||
* No input reflection, no result reflection, no final XOR operation
|
||||
* Examples:
|
||||
* `0x01, 0x02, 0x03, 0x04` => `0x61`
|
||||
* `0x05, 0x04, 0x03, 0x02, 0x01` => `0x64`
|
||||
|
||||
**CRC16**
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Polynomial: `0x3d65`
|
||||
* Initial value: `0x1337` (or `0x0001` for the JSON CRC)
|
||||
* No input reflection, no result reflection, no final XOR operation
|
||||
* Examples:
|
||||
* `0x01, 0x02, 0x03, 0x04` => `0x672E`
|
||||
* `0x05, 0x04, 0x03, 0x02, 0x01` => `0xE251`
|
||||
|
||||
You can use this `online calculator <http://www.sunshine2k.de/coding/javascript/crc/crc_js.html>`__ to verify your implementation.
|
||||
43
docs/reStructuredText/rc-pwm.rst
Normal file
@@ -0,0 +1,43 @@
|
||||
.. _rc-pwm-doc:
|
||||
|
||||
================================================================================
|
||||
RC PWM input
|
||||
================================================================================
|
||||
|
||||
You can control the ODrive directly from a hobby RC receiver.
|
||||
|
||||
Any of the numerical parameters that are writable from the ODrive Tool can be hooked up to a PWM input.
|
||||
The :ref:`Pinout <pinout-chart>` tells you which pins are PWM input capable. As an example, we'll configure GPIO4 to control the angle of axis 0.
|
||||
We want the axis to move within a range of -2 to 2 turns.
|
||||
|
||||
#. Make sure you're able control the axis 0 angle by writing to :code:`odrv0.axis0.controller.input_pos`.
|
||||
If you need help with this follow the :ref:`getting started guide <getting-started>`.
|
||||
#. If you want to control your ODrive with the PWM input without using anything else to activate the ODrive, you can configure the ODrive such that axis 0 automatically goes operational at startup.
|
||||
See :ref:`here <commands-startup-procedure>` for more information.
|
||||
#. In ODrive Tool, configure the PWM input mapping
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.config.gpio4_mode = GPIO_MODE_PWM
|
||||
odrv0.config.gpio4_pwm_mapping.min = -2
|
||||
odrv0.config.gpio4_pwm_mapping.max = 2
|
||||
odrv0.config.gpio4_pwm_mapping.endpoint = odrv0.axis0.controller._input_pos_property
|
||||
|
||||
.. note::
|
||||
|
||||
you can disable the input by setting :code:`odrv0.config.gpio4_pwm_mapping.endpoint = None`
|
||||
|
||||
#. Save the configuration and reboot
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
odrv0.save_configuration()
|
||||
odrv0.reboot()
|
||||
|
||||
#. With the ODrive powered off, connect the RC receiver ground to the ODrive's GND and one of the RC receiver signals to GPIO4.
|
||||
You may try to power the receiver from the ODrive's 5V supply if it doesn't draw too much power. Power up the the RC transmitter.
|
||||
You should now be able to control axis 0 from one of the RC sticks.
|
||||
|
||||
Be sure to setup the Failsafe feature on your RC Receiver so that if connection is lost between the remote and the receiver, the receiver outputs 0 for the velocity setpoint of both axes (or whatever is safest for your configuration).
|
||||
Also note that if the receiver turns off (loss of power, etc) or if the signal from the receiver to the ODrive is lost (wire comes unplugged, etc), the ODrive will continue the last commanded velocity setpoint.
|
||||
There is currently no timeout function in the ODrive for PWM inputs.
|
||||
46
docs/reStructuredText/specifications.rst
Normal file
@@ -0,0 +1,46 @@
|
||||
================================================================================
|
||||
Specifications
|
||||
================================================================================
|
||||
|
||||
Electrical Specifications
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Besides the input voltage range, (12V to 24V for ODrive v3.6 24V, 12V to 56V for ODrive v3.6 56V), the electrical specifications of both version of ODrive v3.6 are the same.
|
||||
|
||||
.. note:: ODrive versions after v3.5 are closed-source with respect to board files and schematics.
|
||||
|
||||
ODrive v3.6 24V and 56V
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Peak current per motor: 120 Amps
|
||||
* Max continuous current depends on cooling. See `this <https://discourse.odriverobotics.com/t/odrive-mosfet-temperature-rise-measurements-using-the-onboard-thermistor/972>`__ for more details.
|
||||
* Heatsink in still air: 40A per channel
|
||||
* Heatsink with basic fan cooling: 75A per channel
|
||||
* Heatsink with overkill fan cooling: 90A per channel
|
||||
* Max motor RPM: This depends on your power supply voltage, motor, and encoder. It is the lesser of:
|
||||
* motor RPM limit
|
||||
* encoder RPM limit
|
||||
* motor KV * 0.7 * Supply voltage
|
||||
* 35000 eRPM / # of motor pole pairs
|
||||
* (840M counts/minute) / encoder counts per revolution (for incremental encoders - 4 x pulses per revolution).
|
||||
|
||||
Schematic
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The electrical schematic for ODrive v3.5 is available `here <https://github.com/madcowswe/ODriveHardware/blob/master/v3/v3.5docs/schematic_v3.5.pdf>`__ in PDF format.
|
||||
|
||||
Mechanical Specifications
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
STEP File
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A step file for ODrive v3.5 is available `here <https://github.com/madcowswe/ODriveHardware/blob/master/v3/v3.5docs/PCB_v3.5.step>`__.
|
||||
|
||||
Board Outline and Mounting Hole Dimensions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. image:: figures/mech_dimensions.png
|
||||
:scale: 30 %
|
||||
:align: center
|
||||
:alt: board dimensions
|
||||
63
docs/reStructuredText/step-direction.rst
Normal file
@@ -0,0 +1,63 @@
|
||||
================================================================================
|
||||
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.
|
||||
|
||||
Pinout
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Step/dir signals: Any GPIOs can be used. Also see :ref:`Pinout <pinout-chart>` for more info.
|
||||
* GND: you must connect the grounds of the devices together. Use any GND pin on J3 of the ODrive.
|
||||
|
||||
How to Configure
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
#. Choose any two of the unused GPIOs for step/dir input.
|
||||
Let's say you chose GPIO7 for the step signal and GPIO8 for the dir signal.
|
||||
#. Configure the GPIO modes:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.config.gpio7_mode = GPIO_MODE_DIGITAL_PULL_DOWN
|
||||
<odrv>.config.gpio8_mode = GPIO_MODE_DIGITAL
|
||||
|
||||
#. Configure the axis:
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.config.step_gpio_pin = 7
|
||||
<axis>.config.dir_gpio_pin = 8
|
||||
<axis>.config.enable_step_dir = True
|
||||
|
||||
#. Enable circular setpoints
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<axis>.controller.config.circular_setpoints = True
|
||||
|
||||
After this, step and direction will be enabled when you put the axis into closed loop control.
|
||||
Note that to change out of step/dir, you need to set :code:`<axis>.config.enable_step_dir = False`, go to :code:`AXIS_STATE_IDLE`, and then back into closed loop control.
|
||||
|
||||
Circular setpoints are used to keep floating point error at a manageable error for systems where the motor can rotate large amounts.
|
||||
If the motor is commanded out of the circular range, the position setpoint automatically wraps around to stay in the range.
|
||||
Two parameters are used to control this behavior: :code:`<odrv>.<axis>.controller.config.circular_setpoint_range` and :code:`<odrv>.<axis>.controller.config.steps_per_circular_range`.
|
||||
The circular setpoint range sets the operating range of input_pos, starting at 0.0. The :code:`steps per circular range` setting controls how many steps are needed to traverse the entire range.
|
||||
|
||||
For example, to use 1024 steps per 1 full motor turn, set
|
||||
|
||||
.. code:: iPython
|
||||
|
||||
<odrv>.<axis>.controller.config.circular_setpoint_range = 1.0 #[turns]
|
||||
<odrv>.<axis>.controller.config.steps_per_circular_range = 1024 #[steps]
|
||||
|
||||
|
||||
The circular range is a floating point value and the steps per circular range parameter is an integer.
|
||||
For best results, set both parameters to powers of 2.
|
||||
|
||||
The maximum step rate is pending tests, but 250kHz step rates with both axes in closed loop has been achieved.
|
||||
|
||||
Please be aware that there is no enable line right now, and the step/direction interface is enabled by default, and remains active as long as the ODrive is in position control mode.
|
||||
To get the ODrive to go into position control mode at bootup, see how to configure the :ref:`startup procedure <commands-startup-procedure>`.
|
||||
208
docs/reStructuredText/testing.rst
Normal file
@@ -0,0 +1,208 @@
|
||||
|
||||
Automated Testing
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
This section describes how to use the automated testing facilities.
|
||||
You don't have to do this as an end user.
|
||||
|
||||
The testing facility consists of the following components:
|
||||
|
||||
* **Test rig:** In the simplest case this can be a single ODrive optionally with a single motor and encoder pair. Can also be multiple ODrives with multiple axes, some of which may be mechanically coupled.
|
||||
* **Test host:** The PC on which the test script runs. All ODrives must be connected to the test host via USB.
|
||||
* **test-rig.yaml:** Describes your test rig. Make sure all values are correct. Incorrect values may physically break or fry your test setup.
|
||||
* **test_runner.py:** This is the main script that runs all the tests.
|
||||
* **..._test.py** The actual tests
|
||||
|
||||
The Tests
|
||||
********************************************************************************
|
||||
|
||||
* :code:`analog_input_test.py`: Analog Input
|
||||
* :code:`calibration_test.py`: Motor calibration, encoder offset calibration, encoder direction find, encoder index search
|
||||
* :code:`can_test.py`: Partial coverage of the commands described in :ref:`CAN Protocol. <can-protocol>`
|
||||
* :code:`closed_loop_test.py`: Velocity control, position control (TODO: sensorless control), brake regen current hard limit, current control with velocity limiting
|
||||
* :code:`encoder_test.py`: Incremental encoder, hall effect encoder, sin/cos encoder, SPI encoders (AMS, CUI)
|
||||
* :code:`fibre_test.py`: General USB protocol tests
|
||||
* :code:`nvm_test.py`: Configuration storage
|
||||
* :code:`pwm_input_test.py`: PWM input
|
||||
* :code:`step_dir_test.py`: Step/dir input
|
||||
* :code:`uart_ascii_test.py`: Partial coverage of the commands described in :ref:`ASCII Protocol <ascii-protocol>`
|
||||
|
||||
All tests in a file can be run with e.g.:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
python3 uart_ascii_test.py --test-rig-yaml ../../test-rig-rpi.yaml
|
||||
|
||||
See the following sections for a more detailed test flow description.
|
||||
|
||||
Our Test Rig
|
||||
**************************************************************************
|
||||
|
||||
Our test rig essentially consists of the following components:
|
||||
|
||||
* an ODrive as the test subject
|
||||
* a Teensy 4.0 to emulate external hardware such as encoders
|
||||
* a Motor + Encoder pair for closed loop control tests
|
||||
* a Raspberry Pi 4.0 as test host
|
||||
* a CAN hat for the Raspberry Pi for CAN tests
|
||||
|
||||
This document is therefore centered around this test rig layout.
|
||||
If your test rig differs, you may be able to run some but not all of the tests.
|
||||
|
||||
How to set up a Raspberry Pi as testing host
|
||||
**************************************************************************
|
||||
|
||||
#. Install Raspbian Lite on a Raspberry Pi 4.0. This is easiest if you have a keyboard, mouse and screen (micro-HDMI!).
|
||||
I used the `NOOBS Lite installer <https://www.raspberrypi.org/downloads/noobs/>`_ for this. Paste the ZIP-file's contents onto a FAT32 formatted SD card (fs type `0b` in `fdisk`) and boot it. Then follow the on-screen instructions.
|
||||
|
||||
#. Prepare the installation:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo systemctl enable ssh
|
||||
sudo systemctl start ssh
|
||||
# Transfer your public key for passwordless SSH. All subsequent steps can be done via SSH.
|
||||
sudo apt-get update
|
||||
sudo apt-get upgrade
|
||||
# Change /etc/hostname to something meaningful
|
||||
|
||||
#. Add the following lines to :code:`/boot/config.txt`:
|
||||
|
||||
* :code:`enable_uart=1`
|
||||
* :code:`dtparam=spi=on`
|
||||
* :code:`dtoverlay=spi-bcm2835-overlay`
|
||||
* :code:`dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25`
|
||||
.. note:: These oscillator and interrupt GPIO settings here are for the "RS485 CAN HAT" I have. There appear to be multiple versions, so they may be different from yours. Check the marking on the oscillator and the schematics.
|
||||
|
||||
#. Remove the following arguments from :code:`/boot/cmdline.txt`:
|
||||
* :code:`console=serial0,115200`
|
||||
|
||||
#. Append :code:`ODRIVE_TEST_RIG_NAME=[test-rig-name]` to :code:`/etc/environment`. The HWIL tests use this to look up the the file :code:`[test-rig-name].yaml` which is supposed to describe your test rig.
|
||||
|
||||
#. Reboot.
|
||||
|
||||
#. Install the prerequisites:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo apt-get install ipython3 python3-appdirs python3-yaml python3-jinja2 python3-usb python3-serial python3-can python3-scipy python3-matplotlib python3-ipdb git openocd
|
||||
# Optionally, to be able to compile the firmware:
|
||||
sudo apt-get install gcc-arm-none-eabi
|
||||
|
||||
#. Install Teensyduino and teensy-loader-cli:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo apt-get install libfontconfig libxft2 libusb-dev
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
wget https://downloads.arduino.cc/arduino-1.8.13-linuxarm.tar.xz
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
tar -xf arduino-1.8.13-linuxarm.tar.xz
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
wget https://www.pjrc.com/teensy/td_153/TeensyduinoInstall.linuxarm
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
chmod +x TeensyduinoInstall.linuxarm
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
./TeensyduinoInstall.linuxarm --dir=arduino-1.8.13
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo cp -R arduino-1.8.13 /usr/share/arduino
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo ln -s /usr/share/arduino/arduino /usr/bin/arduino
|
||||
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
git clone https://github.com/PaulStoffregen/teensy_loader_cli
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
pushd teensy_loader_cli
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
make
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo cp teensy_loader_cli /usr/bin/
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
sudo ln -s /usr/bin/teensy_loader_cli /usr/bin/teensy-loader-cli
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
popd
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
curl https://www.pjrc.com/teensy/49-teensy.rules | sudo tee /etc/udev/rules.d/49-teensy.rules
|
||||
|
||||
#. Add the following lines to :code:`/etc/udev/rules.d/49-stlinkv2.rules`:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", MODE:="0666"
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", MODE:="0666"
|
||||
|
||||
#. :code:`sudo mkdir /opt/odrivetest && sudo chown $USER /opt/odrivetest`
|
||||
|
||||
#. At this point you need the ODrive repository. See next section to sync it from your main PC. We assume now that you navigated to `tools/odrive/tests/`.
|
||||
|
||||
#. :code:`sudo ../../odrivetool udev-setup`
|
||||
|
||||
#. `sudo udevadm trigger`
|
||||
|
||||
#. Run once after every reboot: :code:`sudo -E ipython3 --pdb test_runner.py -- --setup-host`
|
||||
|
||||
SSH Testing Flow
|
||||
**************************************************************************
|
||||
|
||||
Here's one possible workflow for developing on the local host and testing on a remote SSH host.
|
||||
|
||||
We assume that the ODrive repo is at :code:`/path/to/ODriveFirmware` and your testing host is configured under the SSH name :code:`odrv`.
|
||||
|
||||
To flash and start remote debugging:
|
||||
|
||||
#. Start OpenOCD remotely, along with a tunnel to localhost:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
ssh -t odrv -L3333:localhost:3333 bash -c "\"openocd '-f' 'interface/stlink-v2.cfg' '-f' 'target/stm32f4x_stlink.cfg'\""
|
||||
|
||||
|
||||
You can keep this open for multiple debug sessions. Press :kbd:`Ctrl` **+** :kbd:`C`to quit.
|
||||
|
||||
#. Compile the firmware.
|
||||
#. In VSCode, select the run configuration "Debug ODrive v3.x/v4.x - Remote" and press Run. This will flash the new firmware before dropping you into the debugger.
|
||||
|
||||
To run a test:
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
rsync -avh -e ssh /path/to/ODriveFirmware/ odrv:/opt/odrivetest --exclude="Firmware/build" --exclude="Firmware/.tup" --exclude=".git" --exclude="GUI" --delete
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
ssh odrv
|
||||
|
||||
.. code:: Bash
|
||||
|
||||
cd /opt/odrivetest/tools/odrive/tests/
|
||||
ipython3 --pdb uart_ascii_test.py
|
||||
|
||||
67
docs/reStructuredText/thermistors.rst
Normal file
@@ -0,0 +1,67 @@
|
||||
.. _thermistor-doc:
|
||||
|
||||
================================================================================
|
||||
Thermistors
|
||||
================================================================================
|
||||
|
||||
Introduction
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Thermistors are elements that change their resistance based on the temperature.
|
||||
They can be used to electrically measure temperature.
|
||||
The ODrive itself has thermistors on board near the FETs to ensure that they don't burn themselves out.
|
||||
In addition to this it's possible to connect your own thermistor to measure the temperature of the connected motors.
|
||||
There are two types of thermistors, Negative Temperature Coefficient (NTC) and Positive Temperature Coefficient (PTC).
|
||||
This indicates whether the resistance goes up or down when the temperature goes up or down.
|
||||
The ODrive only supports the NTC type thermistor.
|
||||
|
||||
FET Thermistor
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
The temperature of the onboard FET thermistors can be read out by using the :code:`odrivetool` under :code:`<axis>.motor.fet_thermistor.temperature`.
|
||||
The odrive will automatically start current limiting the motor when the :code:`<axis>.motor.fet_thermistor.config.temp_limit_lower` threshold is exceeded and once :code:`<axis>.motor.fet_thermistor.config.temp_limit_upper` is exceeded the ODrive will stop controlling the motor and set an error.
|
||||
The lower and upper threshold can be changed, but this is not recommended.
|
||||
|
||||
Connecting Motor Thermistors
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
To use your own thermistors with the ODrive a few things have to be clarified first.
|
||||
The use of your own thermistor requires one analog input pin. Under :code:`<axis>.motor.motor_thermistor.config` the configuration of your own thermistor is available with the following fields:
|
||||
|
||||
* :code:`gpio_pin`: The GPIO input in used for this thermistor.
|
||||
* :code:`poly_coefficient_0` to :code:`poly_coefficient_3`: Coefficient that needs to be set for your specific setup more on that in :ref:`Thermistor coefficients <thermistor-coefficients>`.
|
||||
* :code:`temp_limit_lower` and :code:`temp_limit_upper`: Same principle as the FET temperature limits.
|
||||
* :code:`enabled`: Whether this thermistor is enabled or not.
|
||||
|
||||
Voltage Divider Circuit
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
To measure a temperature with a thermistor a voltage divider circuit is used in addition with an ADC.
|
||||
The screenshot below is taken directly from the ODrive schematic.
|
||||
|
||||
.. figure:: figures/thermistor-voltage-divider.png
|
||||
:scale: 100 %
|
||||
:alt: Thermistor voltage divider
|
||||
|
||||
The way this works is that the thermistor is connected in series with a known resistance value.
|
||||
By connecting an ADC directly after the thermistor the resistance value can be determined.
|
||||
For further information see `Voltage divider <https://en.wikipedia.org/wiki/Voltage_divider>`_.
|
||||
While not strictly necessary, it is a good idea to add a capacitor as shown as well. This will help reduce the effect of electrical noise.
|
||||
A value between 470nF and 4.7uF is recommended, and any voltage rating 4V or higher. Put the capacitor physically close to the ODrive.
|
||||
To use a thermistor with the ODrive a voltage divider circuit has to be made that uses `VCCA` as the power source with `GNDA` as the ground.
|
||||
The voltage divider output can be connected to a GPIO pin that supports analog input.
|
||||
|
||||
.. _thermistor-coefficients:
|
||||
|
||||
Thermistor Coefficients
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Every thermistor and voltage divider circuit is different and thus it's necessary to let the ODrive know how to relate a voltage it measures at the GPIO pin to a temperature.
|
||||
The :code:`poly_coefficient_0` to :code:`poly_coefficient_3` under :code:`<axis>.motor.motor_thermistor.config` are used for this.
|
||||
The :code:`odrivetool` has a convenience function :code:`set_motor_thermistor_coeffs(axis, Rload, R_25, Beta, Tmin, Tmax)` which can be used to calculate and set these coefficients.
|
||||
|
||||
* :code:`axis`: Which axis do set the motor thermistor coefficients for (:code:`odrv0.axis0` or :code:`odrv0.axis1`).
|
||||
* :code:`Rload`: The Ohm value of the resistor used in the voltage divider circuit.
|
||||
* :code:`R_25`: The resistance of the thermistor when the temperature is 25 degrees celsius. Can usually be found in the datasheet of your thermistor. Can also be measured manually with a multimeter.
|
||||
* :code:`Beta`: A constant specific to your thermistor. Can be found in the datasheet of your thermistor.
|
||||
* :code:`Tmin` and :code:`Tmax`: The temperature range that is used to create the coefficients. Make sure to set this range to be wider than what is expected during operation. A good example may be -10 to 150.
|
||||