mirror of
https://github.com/odriverobotics/ODrive.git
synced 2026-08-20 22:14:34 +08:00
@@ -8,6 +8,12 @@ Please add a note of your changes below this heading if you make a Pull Request.
|
||||
* ASCII protocol commands with multiline responses (`i`, `h`) now return the expected response (in v0.5.2 the response was corrupted)
|
||||
* odrivetool no longer shows the message `<Task pending coro=... running at ...>` when closing
|
||||
|
||||
# Releases
|
||||
## [0.5.3] - unreleased
|
||||
### Fixed
|
||||
* ASCII protocol commands with multiline responses (`i`, `h`) now return the expected response (in v0.5.2 the response was corrupted)
|
||||
* odrivetool no longer shows the message `<Task pending coro=... running at ...>` when closing
|
||||
|
||||
# Releases
|
||||
## [0.5.2] - 2021-05-21
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ sections:
|
||||
url: /hoverboard
|
||||
- title: Migration Guide
|
||||
url: /migration
|
||||
- title: CAN Guide
|
||||
url: /can-guide
|
||||
- title: Interfaces & Protocols
|
||||
docs:
|
||||
- title: Pinout
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
# CAN Bus Guide for ODrive
|
||||
|
||||
ODrive v3 supports CAN 2.0b. We've built a [simple protocol](can-protocol.md) (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!
|
||||
|
||||

|
||||
|
||||
## 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 `odrivetool` before connecting to the bus with the command:
|
||||
|
||||
`<odrv>.<axis>.config.can.node_id = <number>`
|
||||
|
||||
By default, ODrive supports a value up to 63 (`0x3F`). See [can-protocol.md](can-protocol.md) for more information.
|
||||
|
||||
You should also set the CAN bus speed on ODrive with the command `<odrv>.can.config.baud_rate = <number>`
|
||||
|
||||
| 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
|
||||
```Python
|
||||
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:
|
||||
1. Enable SPI communications to the MCP2515
|
||||
2. Install `can-utils` with `apt-get install can-utils`
|
||||
3. Creating a connection between your application and the `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!
|
||||
|
||||
1. 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:
|
||||
```
|
||||
dtparam=spi-on
|
||||
dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25
|
||||
dtoverlay=spi0-hw-cs
|
||||
```
|
||||
|
||||
2. 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.
|
||||
|
||||
```
|
||||
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 `can0` interface, and then reading from it:
|
||||
|
||||
```
|
||||
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 `-n 10` part (hit CTRL+C to exit). The other flags (x, c, t) are adding extra information, colouring, and a timestamp, respectively.
|
||||
|
||||
```
|
||||
$ 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 (`pip3 install python-can`), you can use the can.viewer script:
|
||||
|
||||
`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 `odrivetool` before continuing. See the [Getting Started Guide](getting-started.md) for help with first-time configuration.
|
||||
|
||||
To move the ODrive, we use the command `Set Input Pos`, or cmd ID `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!
|
||||
|
||||
* [CANSimple DBC File](../tools/odrive-cansimple.dbc)
|
||||
* [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.
|
||||
|
||||
1. Load the database into memory
|
||||
2. Use `encode_message()` to get a byte array representation of data for sending
|
||||
3. Use `decode_message()` to get a dictionary representation of data for receiving
|
||||
|
||||
The [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 [CANHelpers](..firmware/communication/../../../Firmware/communication/can/can_helpers.hpp) single-header library to do this instead, although the DBC file isn't used.
|
||||
@@ -1,13 +1,7 @@
|
||||
# CAN Protocol
|
||||
|
||||
## 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.
|
||||
This document describes teh CAN Protocol. For examples of usage, check out our [CAN Guide!](can-guide.md)
|
||||
|
||||
ODrive currently supports the following CAN baud rates:
|
||||
* 125 kbps
|
||||
* 250 kbps (default)
|
||||
* 500 kbps
|
||||
* 1000 kbps
|
||||
|
||||
---
|
||||
## Configuring ODrive for CAN
|
||||
@@ -94,7 +88,7 @@ You can deconflict with CANopen like this:
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -63,7 +63,7 @@ sudo apt-get install python3 python3-yaml python3-jinja2 python3-jsonschema
|
||||
|
||||
#### Linux (Ubuntu >= 20.04)
|
||||
```bash
|
||||
sudo apt install gcc-arm-embedded
|
||||
sudo apt install gcc-arm-none-eabi
|
||||
sudo apt install openocd
|
||||
sudo apt install git-lfs
|
||||
sudo apt install tup
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
@@ -0,0 +1,12 @@
|
||||
import can
|
||||
|
||||
bus1 = can.interface.Bus('can0', bustype='virtual')
|
||||
bus2 = can.interface.Bus('can0', bustype='virtual')
|
||||
|
||||
msg1 = can.Message(arbitration_id=0xabcde, data=[1,2,3])
|
||||
bus1.send(msg1)
|
||||
msg2 = bus2.recv()
|
||||
|
||||
print(hex(msg1.arbitration_id))
|
||||
print(hex(msg2.arbitration_id))
|
||||
assert msg1.arbitration_id == msg2.arbitration_id
|
||||
@@ -0,0 +1,80 @@
|
||||
import math
|
||||
import can
|
||||
import cantools
|
||||
import time
|
||||
|
||||
db = cantools.database.load_file("odrive-cansimple.dbc")
|
||||
# print(db)
|
||||
|
||||
# bus = can.Bus("vcan0", bustype="virtual")
|
||||
bus = can.Bus("can0", bustype="socketcan")
|
||||
axisID = 0x1
|
||||
|
||||
print("\nRequesting AXIS_STATE_FULL_CALIBRATION_SEQUENCE (0x03) on axisID: " + str(axisID))
|
||||
msg = db.get_message_by_name('Set_Axis_State')
|
||||
data = msg.encode({'Axis_Requested_State': 0x03})
|
||||
msg = can.Message(arbitration_id=msg.frame_id | axisID << 5, is_extended_id=False, data=data)
|
||||
print(db.decode_message('Set_Axis_State', msg.data))
|
||||
print(msg)
|
||||
|
||||
try:
|
||||
bus.send(msg)
|
||||
print("Message sent on {}".format(bus.channel_info))
|
||||
except can.CanError:
|
||||
print("Message NOT sent! Please verify can0 is working first")
|
||||
|
||||
print("Waiting for calibration to finish...")
|
||||
# Read messages infinitely and wait for the right ID to show up
|
||||
while True:
|
||||
msg = bus.recv()
|
||||
if msg.arbitration_id == ((axisID << 5) | db.get_message_by_name('Heartbeat').frame_id):
|
||||
current_state = db.decode_message('Heartbeat', msg.data)['Axis_State']
|
||||
if current_state == 0x1:
|
||||
print("\nAxis has returned to Idle state.")
|
||||
break
|
||||
|
||||
for msg in bus:
|
||||
if msg.arbitration_id == ((axisID << 5) | db.get_message_by_name('Heartbeat').frame_id):
|
||||
errorCode = db.decode_message('Heartbeat', msg.data)['Axis_Error']
|
||||
if errorCode == 0x00:
|
||||
print("No errors")
|
||||
else:
|
||||
print("Axis error! Error code: "+str(hex(errorCode)))
|
||||
break
|
||||
|
||||
print("\nPutting axis",axisID,"into AXIS_STATE_CLOSED_LOOP_CONTROL (0x08)...")
|
||||
data = db.encode_message('Set_Axis_State', {'Axis_Requested_State': 0x08})
|
||||
msg = can.Message(arbitration_id=0x07 | axisID << 5, is_extended_id=False, data=data)
|
||||
print(msg)
|
||||
|
||||
try:
|
||||
bus.send(msg)
|
||||
print("Message sent on {}".format(bus.channel_info))
|
||||
except can.CanError:
|
||||
print("Message NOT sent!")
|
||||
|
||||
for msg in bus:
|
||||
if msg.arbitration_id == 0x01 | axisID << 5:
|
||||
print("\nReceived Axis heartbeat message:")
|
||||
msg = db.decode_message('Heartbeat', msg.data)
|
||||
print(msg)
|
||||
if msg['Axis_State'] == 0x8:
|
||||
print("Axis has entered closed loop")
|
||||
else:
|
||||
print("Axis failed to enter closed loop")
|
||||
break
|
||||
|
||||
target = 0
|
||||
|
||||
data = db.encode_message('Set_Limits', {'Velocity_Limit':10.0, 'Current_Limit':10.0})
|
||||
msg = can.Message(arbitration_id=axisID << 5 | 0x00F, is_extended_id=False, data=data)
|
||||
bus.send(msg)
|
||||
|
||||
t0 = time.monotonic()
|
||||
while True:
|
||||
setpoint = 4.0 * math.sin((time.monotonic() - t0)*2)
|
||||
print("goto " + str(setpoint))
|
||||
data = db.encode_message('Set_Input_Pos', {'Input_Pos':setpoint, 'Vel_FF':0.0, 'Torque_FF':0.0})
|
||||
msg = can.Message(arbitration_id=axisID << 5 | 0x00C, data=data, is_extended_id=False)
|
||||
bus.send(msg)
|
||||
time.sleep(0.01)
|
||||
@@ -0,0 +1,53 @@
|
||||
import can
|
||||
|
||||
bus = can.Bus("can0", bustype="socketcan")
|
||||
axisID = 0x1
|
||||
|
||||
print("Requesting AXIS_STATE_FULL_CALIBRATION_SEQUENCE (0x03) on axisID: " + str(axisID))
|
||||
msg = can.Message(arbitration_id=axisID << 5 | 0x07, data=[3, 0, 0, 0, 0, 0, 0, 0], dlc=8, is_extended_id=False)
|
||||
print(msg)
|
||||
|
||||
try:
|
||||
bus.send(msg)
|
||||
print("Message sent on {}".format(bus.channel_info))
|
||||
except can.CanError:
|
||||
print("Message NOT sent! Please verify can0 is working first")
|
||||
|
||||
print("Waiting for calibration to finish...")
|
||||
# Read messages infinitely and wait for the right ID to show up
|
||||
while True:
|
||||
msg = bus.recv()
|
||||
if msg.arbitration_id == (axisID << 5 | 0x01):
|
||||
current_state = msg.data[4] | msg.data[5] << 8 | msg.data[6] << 16 | msg.data[7] << 24
|
||||
if current_state == 0x1:
|
||||
print("\nAxis has returned to Idle state.")
|
||||
break
|
||||
|
||||
for msg in bus:
|
||||
if(msg.arbitration_id == (axisID << 5 | 0x01)):
|
||||
errorCode = msg.data[0] | msg.data[1] << 8 | msg.data[2] << 16 | msg.data[3] << 24
|
||||
print("\nReceived Axis heartbeat message:")
|
||||
if errorCode == 0x0:
|
||||
print("No errors")
|
||||
else:
|
||||
print("Axis error! Error code: "+str(hex(errorCode)))
|
||||
break
|
||||
|
||||
print("\nPutting axis",axisID,"into AXIS_STATE_CLOSED_LOOP_CONTROL (0x08)...")
|
||||
msg = can.Message(arbitration_id=axisID << 5 | 0x07, data=[8, 0, 0, 0, 0, 0, 0, 0], dlc=8, is_extended_id=False)
|
||||
print(msg)
|
||||
|
||||
try:
|
||||
bus.send(msg)
|
||||
print("Message sent on {}".format(bus.channel_info))
|
||||
except can.CanError:
|
||||
print("Message NOT sent!")
|
||||
|
||||
for msg in bus:
|
||||
if msg.arbitration_id == (axisID << 5 | 0x01):
|
||||
print("\nReceived Axis heartbeat message:")
|
||||
if msg.data[4] == 0x8:
|
||||
print("Axis has entered closed loop")
|
||||
else:
|
||||
print("Axis failed to enter closed loop")
|
||||
break
|
||||
@@ -0,0 +1,166 @@
|
||||
import cantools
|
||||
|
||||
# 0x00 - NMT Message (Reserved)
|
||||
|
||||
# 0x001 - Heartbeat
|
||||
axisError = cantools.database.can.Signal("Axis_Error", 0, 32)
|
||||
axisState = cantools.database.can.Signal("Axis_State", 32, 32)
|
||||
heartbeatMsg = cantools.database.can.Message(
|
||||
0x001, "Heartbeat", 8, [axisError, axisState]
|
||||
)
|
||||
|
||||
# 0x003 - Motor Error
|
||||
motorError = cantools.database.can.Signal("Motor_Error", 0, 32)
|
||||
motorErrorMsg = cantools.database.can.Message(0x003, "Get_Motor_Error", 8, [motorError])
|
||||
|
||||
# 0x004 - Encoder Error
|
||||
encoderError = cantools.database.can.Signal("Encoder_Error", 0, 32)
|
||||
encoderErrorMsg = cantools.database.can.Message(
|
||||
0x004, "Get_Encoder_Error", 8, [encoderError]
|
||||
)
|
||||
|
||||
# 0x005 - Sensorless Error
|
||||
sensorlessError = cantools.database.can.Signal("Sensorless_Error", 0, 32)
|
||||
sensorlessErrorMsg = cantools.database.can.Message(
|
||||
0x005, "Get_Sensorless_Error", 8, [sensorlessError]
|
||||
)
|
||||
|
||||
# 0x006 - Axis Node ID
|
||||
axisNodeID = cantools.database.can.Signal("Axis_Node_ID", 0, 32)
|
||||
axisNodeMsg = cantools.database.can.Message(0x006, "Set_Axis_Node_ID", 8, [axisNodeID])
|
||||
|
||||
# 0x007 - Requested State
|
||||
axisRequestedState = cantools.database.can.Signal("Axis_Requested_State", 0, 32)
|
||||
setAxisState = cantools.database.can.Message(
|
||||
0x007, "Set_Axis_State", 8, [axisRequestedState]
|
||||
)
|
||||
|
||||
# 0x008 - Startup Config (Reserved)
|
||||
|
||||
# 0x009 - Encoder Estimates
|
||||
encoderPosEstimate = cantools.database.can.Signal("Pos_Estimate", 0, 32, is_float=True)
|
||||
encoderVelEstimate = cantools.database.can.Signal("Vel_Estimate", 32, 32, is_float=True)
|
||||
encoderEstimates = cantools.database.can.Message(
|
||||
0x009, "Get_Encoder_Estimates", 8, [encoderPosEstimate, encoderVelEstimate]
|
||||
)
|
||||
|
||||
|
||||
# 0x00A - Get Encoder Count
|
||||
encoderShadowCount = cantools.database.can.Signal("Shadow_Count", 0, 32)
|
||||
encoderCountInCPR = cantools.database.can.Signal("Count_in_CPR", 32, 32)
|
||||
encoderCountMsg = cantools.database.can.Message(
|
||||
0x00A, "Get_Encoder_Count", 8, [encoderShadowCount, encoderCountInCPR]
|
||||
)
|
||||
|
||||
# 0x00B - Set Controller Modes
|
||||
controlMode = cantools.database.can.Signal("Control_Mode", 0, 32)
|
||||
inputMode = cantools.database.can.Signal("Input_Mode", 32, 32)
|
||||
setControllerModeMsg = cantools.database.can.Message(
|
||||
0x00B, "Set_Controller_Mode", 8, [controlMode, inputMode]
|
||||
)
|
||||
|
||||
# 0x00C - Set Input Pos
|
||||
inputPos = cantools.database.can.Signal("Input_Pos", 0, 32, is_float=True)
|
||||
velFF = cantools.database.can.Signal("Vel_FF", 32, 16, is_signed=True, scale=0.001)
|
||||
torqueFF = cantools.database.can.Signal(
|
||||
"Torque_FF", 48, 16, is_signed=True, scale=0.001
|
||||
)
|
||||
setInputPosMsg = cantools.database.can.Message(
|
||||
0x00C, "Set_Input_Pos", 8, [inputPos, velFF, torqueFF]
|
||||
)
|
||||
|
||||
# 0x00D - Set Input Vel
|
||||
inputVel = cantools.database.can.Signal("Input_Vel", 0, 32, is_float=True)
|
||||
inputTorqueFF = cantools.database.can.Signal("Input_Torque_FF", 32, 32, is_float=True)
|
||||
setInputVelMsg = cantools.database.can.Message(
|
||||
0x00D, "Set_Input_Vel", 8, [inputVel, inputTorqueFF]
|
||||
)
|
||||
|
||||
# 0x00E - Set Input Torque
|
||||
inputTorque = cantools.database.can.Signal("Input_Torque", 0, 32, is_float=True)
|
||||
setInputTqMsg = cantools.database.can.Message(
|
||||
0x00E, "Set_Input_Torque", 8, [inputTorque]
|
||||
)
|
||||
|
||||
# 0x00F - Set Velocity Limit
|
||||
velLimit = cantools.database.can.Signal("Velocity_Limit", 0, 32, is_float=True)
|
||||
currentLimit = cantools.database.can.Signal("Current_Limit", 32, 32, is_float=True)
|
||||
setVelLimMsg = cantools.database.can.Message(0x00F, "Set_Limits", 8, [velLimit, currentLimit])
|
||||
|
||||
# 0x010 - Start Anticogging
|
||||
startAnticoggingMsg = cantools.database.can.Message(0x010, "Start_Anticogging", 0, [])
|
||||
|
||||
# 0x011 - Set Traj Vel Limit
|
||||
trajVelLim = cantools.database.can.Signal("Traj_Vel_Limit", 0, 32, is_float=True)
|
||||
setTrajVelMsg = cantools.database.can.Message(
|
||||
0x011, "Set_Traj_Vel_Limit", 8, [trajVelLim]
|
||||
)
|
||||
|
||||
# 0x012 - Set Traj Accel Limits
|
||||
trajAccelLim = cantools.database.can.Signal("Traj_Accel_Limit", 0, 32, is_float=True)
|
||||
trajDecelLim = cantools.database.can.Signal("Traj_Decel_Limit", 32, 32, is_float=True)
|
||||
setTrajAccelMsg = cantools.database.can.Message(
|
||||
0x012, "Set_Traj_Accel_Limits", 8, [trajAccelLim, trajDecelLim]
|
||||
)
|
||||
|
||||
# 0x013 - Set Traj Inertia
|
||||
trajInertia = cantools.database.can.Signal("Traj_Inertia", 0, 32, is_float=True)
|
||||
trajInertiaMsg = cantools.database.can.Message(
|
||||
0x013, "Set_Traj_Inertia", 8, [trajInertia]
|
||||
)
|
||||
|
||||
# 0x014 - Get Iq
|
||||
iqSetpoint = cantools.database.can.Signal("Iq_Setpoint", 0, 32, is_float=True)
|
||||
iqMeasured = cantools.database.can.Signal("Iq_Measured", 32, 32, is_float=True)
|
||||
getIqMsg = cantools.database.can.Message(0x014, "Get_Iq", 8, [iqSetpoint, iqMeasured])
|
||||
|
||||
# 0x015 - Get Sensorless Estimates
|
||||
sensorlessPosEstimate = cantools.database.can.Signal(
|
||||
"Sensorless_Pos_Estimate", 0, 32, is_float=True
|
||||
)
|
||||
sensorlessVelEstimate = cantools.database.can.Signal(
|
||||
"Sensorless_Vel_Estimate", 32, 32, is_float=True
|
||||
)
|
||||
getSensorlessEstMsg = cantools.database.can.Message(
|
||||
0x015, "Get_Sensorless_Estimates", 8, [sensorlessPosEstimate, sensorlessVelEstimate]
|
||||
)
|
||||
|
||||
# 0x016 - Reboot ODrive
|
||||
rebootMsg = cantools.database.can.Message(0x016, "Reboot", 0, [])
|
||||
|
||||
# 0x017 - Get vbus Voltage
|
||||
vbusVoltage = cantools.database.can.Signal("Vbus_Voltage", 0, 32, is_float=True)
|
||||
getVbusVMsg = cantools.database.can.Message(0x017, "Get_Vbus_Voltage", 8, [vbusVoltage])
|
||||
|
||||
# 0x018 - Clear Errors
|
||||
clearErrorsMsg = cantools.database.can.Message(0x018, "Clear_Errors", 0, [])
|
||||
|
||||
db = cantools.database.can.Database(
|
||||
[
|
||||
heartbeatMsg,
|
||||
motorErrorMsg,
|
||||
encoderErrorMsg,
|
||||
sensorlessErrorMsg,
|
||||
axisNodeMsg,
|
||||
setAxisState,
|
||||
encoderEstimates,
|
||||
encoderCountMsg,
|
||||
setControllerModeMsg,
|
||||
setInputPosMsg,
|
||||
setInputVelMsg,
|
||||
setInputTqMsg,
|
||||
setVelLimMsg,
|
||||
startAnticoggingMsg,
|
||||
setTrajVelMsg,
|
||||
setTrajAccelMsg,
|
||||
trajInertiaMsg,
|
||||
getIqMsg,
|
||||
getSensorlessEstMsg,
|
||||
rebootMsg,
|
||||
getVbusVMsg,
|
||||
]
|
||||
)
|
||||
|
||||
cantools.database.dump_file(db, "odrive-cansimple.dbc")
|
||||
db = cantools.database.load_file("odrive-cansimple.dbc")
|
||||
print(db)
|
||||
@@ -0,0 +1,137 @@
|
||||
VERSION ""
|
||||
|
||||
|
||||
NS_ :
|
||||
NS_DESC_
|
||||
CM_
|
||||
BA_DEF_
|
||||
BA_
|
||||
VAL_
|
||||
CAT_DEF_
|
||||
CAT_
|
||||
FILTER
|
||||
BA_DEF_DEF_
|
||||
EV_DATA_
|
||||
ENVVAR_DATA_
|
||||
SGTYPE_
|
||||
SGTYPE_VAL_
|
||||
BA_DEF_SGTYPE_
|
||||
BA_SGTYPE_
|
||||
SIG_TYPE_REF_
|
||||
VAL_TABLE_
|
||||
SIG_GROUP_
|
||||
SIG_VALTYPE_
|
||||
SIGTYPE_VALTYPE_
|
||||
BO_TX_BU_
|
||||
BA_DEF_REL_
|
||||
BA_REL_
|
||||
BA_DEF_DEF_REL_
|
||||
BU_SG_REL_
|
||||
BU_EV_REL_
|
||||
BU_BO_REL_
|
||||
SG_MUL_VAL_
|
||||
|
||||
BS_:
|
||||
|
||||
BU_:
|
||||
|
||||
|
||||
BO_ 1 Heartbeat: 8 Vector__XXX
|
||||
SG_ Axis_State : 32|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
SG_ Axis_Error : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 3 Get_Motor_Error: 8 Vector__XXX
|
||||
SG_ Motor_Error : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 4 Get_Encoder_Error: 8 Vector__XXX
|
||||
SG_ Encoder_Error : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 5 Get_Sensorless_Error: 8 Vector__XXX
|
||||
SG_ Sensorless_Error : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 6 Set_Axis_Node_ID: 8 Vector__XXX
|
||||
SG_ Axis_Node_ID : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 7 Set_Axis_State: 8 Vector__XXX
|
||||
SG_ Axis_Requested_State : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 9 Get_Encoder_Estimates: 8 Vector__XXX
|
||||
SG_ Vel_Estimate : 32|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
SG_ Pos_Estimate : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 10 Get_Encoder_Count: 8 Vector__XXX
|
||||
SG_ Count_in_CPR : 32|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
SG_ Shadow_Count : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 11 Set_Controller_Mode: 8 Vector__XXX
|
||||
SG_ Input_Mode : 32|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
SG_ Control_Mode : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 12 Set_Input_Pos: 8 Vector__XXX
|
||||
SG_ Torque_FF : 48|16@1- (0.001,0) [0|0] "" Vector__XXX
|
||||
SG_ Vel_FF : 32|16@1- (0.001,0) [0|0] "" Vector__XXX
|
||||
SG_ Input_Pos : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 13 Set_Input_Vel: 8 Vector__XXX
|
||||
SG_ Input_Torque_FF : 32|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
SG_ Input_Vel : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 14 Set_Input_Torque: 8 Vector__XXX
|
||||
SG_ Input_Torque : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 15 Set_Limits: 8 Vector__XXX
|
||||
SG_ Current_Limit : 32|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
SG_ Velocity_Limit : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 16 Start_Anticogging: 0 Vector__XXX
|
||||
|
||||
BO_ 17 Set_Traj_Vel_Limit: 8 Vector__XXX
|
||||
SG_ Traj_Vel_Limit : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 18 Set_Traj_Accel_Limits: 8 Vector__XXX
|
||||
SG_ Traj_Decel_Limit : 32|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
SG_ Traj_Accel_Limit : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 19 Set_Traj_Inertia: 8 Vector__XXX
|
||||
SG_ Traj_Inertia : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 20 Get_Iq: 8 Vector__XXX
|
||||
SG_ Iq_Measured : 32|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
SG_ Iq_Setpoint : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 21 Get_Sensorless_Estimates: 8 Vector__XXX
|
||||
SG_ Sensorless_Vel_Estimate : 32|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
SG_ Sensorless_Pos_Estimate : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
BO_ 22 Reboot: 0 Vector__XXX
|
||||
|
||||
BO_ 23 Get_Vbus_Voltage: 8 Vector__XXX
|
||||
SG_ Vbus_Voltage : 0|32@1+ (1,0) [0|0] "" Vector__XXX
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
SIG_VALTYPE_ 9 Pos_Estimate : 1;
|
||||
SIG_VALTYPE_ 9 Vel_Estimate : 1;
|
||||
SIG_VALTYPE_ 12 Input_Pos : 1;
|
||||
SIG_VALTYPE_ 13 Input_Vel : 1;
|
||||
SIG_VALTYPE_ 13 Input_Torque_FF : 1;
|
||||
SIG_VALTYPE_ 14 Input_Torque : 1;
|
||||
SIG_VALTYPE_ 15 Velocity_Limit : 1;
|
||||
SIG_VALTYPE_ 15 Current_Limit : 1;
|
||||
SIG_VALTYPE_ 17 Traj_Vel_Limit : 1;
|
||||
SIG_VALTYPE_ 18 Traj_Accel_Limit : 1;
|
||||
SIG_VALTYPE_ 18 Traj_Decel_Limit : 1;
|
||||
SIG_VALTYPE_ 19 Traj_Inertia : 1;
|
||||
SIG_VALTYPE_ 20 Iq_Setpoint : 1;
|
||||
SIG_VALTYPE_ 20 Iq_Measured : 1;
|
||||
SIG_VALTYPE_ 21 Sensorless_Pos_Estimate : 1;
|
||||
SIG_VALTYPE_ 21 Sensorless_Vel_Estimate : 1;
|
||||
SIG_VALTYPE_ 23 Vbus_Voltage : 1;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ for i in [1,2,3,4]:
|
||||
# A sine wave to test
|
||||
t0 = time.monotonic()
|
||||
while True:
|
||||
setpoint = 10000.0 * math.sin((time.monotonic() - t0)*2)
|
||||
setpoint = 4.0 * math.sin((time.monotonic() - t0)*2)
|
||||
print("goto " + str(int(setpoint)))
|
||||
my_drive.axis0.controller.pos_setpoint = setpoint
|
||||
time.sleep(0.01)
|
||||
|
||||
Reference in New Issue
Block a user