Merge pull request #730 from odriverobotics/devel

Fw v0.5.6 Release Candidate
This commit is contained in:
Paul Guenette
2023-04-29 12:23:31 -07:00
committed by GitHub
21 changed files with 1632 additions and 381 deletions
+1
View File
@@ -65,3 +65,4 @@ GUI/node_modules
GUI/build
docs/reStructuredText/_build/
tools/odrive-cansimple.ini
+38 -3
View File
@@ -1,7 +1,42 @@
# Unreleased Features
Please add a note of your changes below this heading if you make a Pull Request.
# Releases
## [0.5.6] - Unreleased
### Fixed
* Fixed race condition in homing sequence that was causing strange behaviour. Fixes [#634](https://github.com/odriverobotics/ODrive/issues/634)]
* When using a load encoder, CAN will report the correct position and velocity.
* When using a load encoder, homing will reset the correct linear position. Fixes [#651](https://github.com/odriverobotics/ODrive/issues/651)
* Implemented CAN controller error message, which was previously defined but not actually implemented.
* Get Vbus Voltage message updated to match ODrive Pro's CANSimple implementation.
* `vel_setpoint` and `torque_setpoint` will be clamped to `vel_limit` and the active torque limit. Fixes [#647](https://github.com/odriverobotics/ODrive/issues/647)
### Added
* Added public `controller.get_anticogging_value(uint32)` fibre function to index into the the cogging map. Fixes [#690](https://github.com/odriverobotics/ODrive/issues/690)
* Added Get ADC Voltage message to CAN (0x1C). Send the desired GPIO number in byte 1, and the ODrive will respond with the ADC voltage from that pin (if previously configured for analog)
* Added CAN heartbeat message flags for motor, controller, and encoder error. If flag is true, fetch the corresponding error with the respective message.
* Added scoped enums, e.g. `CONTROL_MODE_POSITION_CONTROL` can be used as `ControlMode.POSITION_CONTROL`
* Added more cyclic messages to can. Use the `rate_ms` values in `<odrv>.<axis>.config.can` to set the cycle rate of the message in milliseconds. Set a rate to 0 to disable sending. The following variables are avaialble:
Command ID | Rate Variable | Message Name
:-- | :-- | :--
0x01 | `heartbeat_rate_ms` | Heartbeat
0x09 | `encoder_rate_ms` | Get Encoder Estimates
0x03 | `motor_error_rate_ms` | Get Motor Error
0x04 | `encoder_error_rate_ms` | Get Encoder Error
0x1D | `controller_error_rate_ms` | Get Controller Error
0x05 | `sensorless_error_rate_ms` | Get Sensorless Error
0x0A | `encoder_count_rate_ms` | Get Encoder Count
0x14 | `iq_rate_ms` | Get Iq
0x15 | `sensorless_rate_ms` | Get Sensorless Estimates
0x17 | `bus_vi_rate_ms` | Get Bus Voltage Current
### Changed
* Improved can_generate_dbc.py file and resultant .dbc. Now supports 8 ODrive axes (0..7) natively
* Add units and value tables to every signal in odrive-cansimple.dbc
* Autogenerate odrive-cansimple.dbc on compile
## [0.5.5] - 2022-08-11
* CANSimple messages which previously required the rtr bit to be set will now also respond if DLC = 0
+1
View File
@@ -33,6 +33,7 @@ all:
@tup --quiet -no-environ-check
@$(PY_CMD) interface_generator_stub.py --definitions odrive-interface.yaml --template ../tools/enums_template.j2 --output ../tools/odrive/enums.py
@$(PY_CMD) interface_generator_stub.py --definitions odrive-interface.yaml --template ../tools/arduino_enums_template.j2 --output ../Arduino/ODriveArduino/ODriveEnums.h
@cd ../tools/ && $(PY_CMD) create_can_dbc.py
# Copy libfibre files to odrivetool if they were built
@ ! test -f "fibre-cpp/build/libfibre-linux-amd64.so" || cp fibre-cpp/build/libfibre-linux-amd64.so ../tools/odrive/pyfibre/fibre/
+18 -10
View File
@@ -354,9 +354,6 @@ bool Axis::run_closed_loop_control_loop() {
// Slowly drive in the negative direction at homing_speed until the min endstop is pressed
// When pressed, set the linear count to the offset (default 0), and then go to position 0
bool Axis::run_homing() {
Controller::ControlMode stored_control_mode = controller_.config_.control_mode;
Controller::InputMode stored_input_mode = controller_.config_.input_mode;
// TODO: theoretically this check should be inside the update loop,
// otherwise someone could disable the endstop while homing is in progress.
if (!min_endstop_.config_.enabled) {
@@ -424,13 +421,20 @@ bool Axis::run_homing() {
return false;
}
// Set the current position to 0.
encoder_.set_linear_count(0);
controller_.input_pos_ = 0;
// Set the current position to 0, the target to zero, and make sure we're path planning from 0 to 0
encoder_.set_linear_count(0);
const auto load_encoder_axis = controller_.config_.load_encoder_axis;
if(load_encoder_axis != axis_num_ && load_encoder_axis < AXIS_COUNT) {
axes[load_encoder_axis].encoder_.set_linear_count(0);
}
controller_.input_pos_ = 0.0f;
controller_.pos_setpoint_ = 0.0f;
controller_.vel_setpoint_ = 0.0f;
controller_.input_pos_updated();
controller_.config_.control_mode = stored_control_mode;
controller_.config_.input_mode = stored_input_mode;
// Force encoder estimate to update
osDelay(1);
homing_.is_homed = true;
return check_for_errors();
@@ -540,9 +544,13 @@ void Axis::run_state_machine_loop() {
} break;
case AXIS_STATE_HOMING: {
//if (odrv.any_error())
// goto invalid_state_label;
Controller::ControlMode stored_control_mode = controller_.config_.control_mode;
Controller::InputMode stored_input_mode = controller_.config_.input_mode;
status = run_homing();
controller_.config_.control_mode = stored_control_mode;
controller_.config_.input_mode = stored_input_mode;
} break;
case AXIS_STATE_ENCODER_OFFSET_CALIBRATION: {
+16
View File
@@ -56,6 +56,14 @@ public:
bool is_extended = false;
uint32_t heartbeat_rate_ms = 100;
uint32_t encoder_rate_ms = 10;
uint32_t motor_error_rate_ms = 0;
uint32_t encoder_error_rate_ms = 0;
uint32_t controller_error_rate_ms = 0;
uint32_t sensorless_error_rate_ms = 0;
uint32_t encoder_count_rate_ms = 0;
uint32_t iq_rate_ms = 0;
uint32_t sensorless_rate_ms = 0;
uint32_t bus_vi_rate_ms = 0;
};
struct Config_t {
@@ -101,6 +109,14 @@ public:
struct CAN_t {
uint32_t last_heartbeat = 0;
uint32_t last_encoder = 0;
uint32_t last_motor_error = 0;
uint32_t last_encoder_error = 0;
uint32_t last_controller_error = 0;
uint32_t last_sensorless_error = 0;
uint32_t last_encoder_count = 0;
uint32_t last_iq = 0;
uint32_t last_sensorless = 0;
uint32_t last_bus_vi = 0;
};
Axis(int axis_num,
+22 -1
View File
@@ -1,6 +1,7 @@
#include "odrive_main.h"
#include <algorithm>
#include <numeric>
bool Controller::apply_config() {
config_.parent = this;
@@ -53,6 +54,20 @@ void Controller::start_anticogging_calibration() {
}
}
float Controller::remove_anticogging_bias()
{
auto& cogmap = config_.anticogging.cogging_map;
auto sum = std::accumulate(std::begin(cogmap), std::end(cogmap), 0.0f);
auto average = sum / std::size(cogmap);
for(auto& val : cogmap) {
val -= average;
}
return average;
}
/*
* This anti-cogging implementation iterates through each encoder position,
@@ -267,6 +282,13 @@ bool Controller::update() {
}
// Never command a setpoint beyond its limit
if(config_.enable_vel_limit) {
vel_setpoint_ = std::clamp(vel_setpoint_, -config_.vel_limit, config_.vel_limit);
}
const float Tlim = axis_->motor_.max_available_torque();
torque_setpoint_ = std::clamp(torque_setpoint_, -Tlim, Tlim);
// Position control
// TODO Decide if we want to use encoder or pll position here
float gain_scheduling_multiplier = 1.0f;
@@ -373,7 +395,6 @@ bool Controller::update() {
// Torque limiting
bool limited = false;
float Tlim = axis_->motor_.max_available_torque();
if (torque > Tlim) {
limited = true;
torque = Tlim;
+5
View File
@@ -81,7 +81,12 @@ public:
// TODO: make this more similar to other calibration loops
void start_anticogging_calibration();
float remove_anticogging_bias();
bool anticogging_calibration(float pos_estimate, float vel_estimate);
float get_anticogging_value(uint32_t index) {
return (index < 3600) ? config_.anticogging.cogging_map[index] : 0.0f;
}
void update_filter_gains();
bool update();
-1
View File
@@ -18,7 +18,6 @@ class Endstop {
void set_debounce_ms(uint32_t value) { debounce_ms = value; parent->apply_config(); }
};
Endstop() {}
Endstop::Config_t config_;
Axis* axis_ = nullptr;
+14 -7
View File
@@ -328,9 +328,16 @@ boards = {
-- Toolchain setup -------------------------------------------------------------
CC='arm-none-eabi-gcc -std=c99'
CXX='arm-none-eabi-g++ -std=c++17 -Wno-register'
LINKER='arm-none-eabi-g++'
CCPATH = tup.getconfig('ARM_COMPILER_PATH')
if CCPATH == "" then
CCPATH=''
else
CCPATH = CCPATH..'/'
end
CC=CCPATH..'arm-none-eabi-gcc -std=c99'
CXX=CCPATH..'arm-none-eabi-g++ -std=c++17 -Wno-register'
LINKER=CCPATH..'arm-none-eabi-g++'
-- C-specific flags
CFLAGS += '-D__weak="__attribute__((weak))"'
@@ -440,13 +447,13 @@ tup.frule{
outputs={'build/ODriveFirmware.elf', extra_outputs={'build/ODriveFirmware.map'}}
}
-- display the size
tup.frule{inputs={'build/ODriveFirmware.elf'}, command='arm-none-eabi-size %f'}
tup.frule{inputs={'build/ODriveFirmware.elf'}, command=CCPATH..'arm-none-eabi-size %f'}
-- create *.hex and *.bin output formats
tup.frule{inputs={'build/ODriveFirmware.elf'}, command='arm-none-eabi-objcopy -O ihex %f %o', outputs={'build/ODriveFirmware.hex'}}
tup.frule{inputs={'build/ODriveFirmware.elf'}, command='arm-none-eabi-objcopy -O binary -S %f %o', outputs={'build/ODriveFirmware.bin'}}
tup.frule{inputs={'build/ODriveFirmware.elf'}, command=CCPATH..'arm-none-eabi-objcopy -O ihex %f %o', outputs={'build/ODriveFirmware.hex'}}
tup.frule{inputs={'build/ODriveFirmware.elf'}, command=CCPATH..'arm-none-eabi-objcopy -O binary -S %f %o', outputs={'build/ODriveFirmware.bin'}}
if tup.getconfig('ENABLE_DISASM') == 'true' then
tup.frule{inputs={'build/ODriveFirmware.elf'}, command='arm-none-eabi-objdump %f -dSC > %o', outputs={'build/ODriveFirmware.asm'}}
tup.frule{inputs={'build/ODriveFirmware.elf'}, command=CCPATH..'arm-none-eabi-objdump %f -dSC > %o', outputs={'build/ODriveFirmware.asm'}}
end
if tup.getconfig('DOCTEST') == 'true' then
+80 -31
View File
@@ -2,6 +2,7 @@
#include "can_simple.hpp"
#include <odrive_main.h>
#include <functional>
bool CANSimple::init() {
for (size_t i = 0; i < AXIS_COUNT; ++i) {
@@ -135,9 +136,9 @@ void CANSimple::do_command(Axis& axis, const can_Message_t& msg) {
case MSG_RESET_ODRIVE:
NVIC_SystemReset();
break;
case MSG_GET_VBUS_VOLTAGE:
case MSG_GET_BUS_VOLTAGE_CURRENT:
if (msg.rtr || msg.len == 0)
get_vbus_voltage_callback(axis);
get_bus_voltage_current_callback(axis);
break;
case MSG_CLEAR_ERRORS:
clear_errors_callback(axis, msg);
@@ -151,6 +152,12 @@ void CANSimple::do_command(Axis& axis, const can_Message_t& msg) {
case MSG_SET_VEL_GAINS:
set_vel_gains_callback(axis, msg);
break;
case MSG_GET_ADC_VOLTAGE:
get_adc_voltage_callback(axis, msg);
break;
case MSG_GET_CONTROLLER_ERROR:
get_controller_error_callback(axis);
break;
default:
break;
}
@@ -200,6 +207,18 @@ bool CANSimple::get_sensorless_error_callback(const Axis& axis) {
return canbus_->send_message(txmsg);
}
bool CANSimple::get_controller_error_callback(const Axis& axis) {
can_Message_t txmsg;
txmsg.id = axis.config_.can.node_id << NUM_CMD_ID_BITS;
txmsg.id += MSG_GET_CONTROLLER_ERROR; // heartbeat ID
txmsg.isExt = axis.config_.can.is_extended;
txmsg.len = 8;
can_setSignal(txmsg, axis.controller_.error_, 0, 32, true);
return canbus_->send_message(txmsg);
}
void CANSimple::set_axis_nodeid_callback(Axis& axis, const can_Message_t& msg) {
axis.config_.can.node_id = can_getSignal<uint32_t>(msg, 0, 32, true);
}
@@ -219,8 +238,8 @@ bool CANSimple::get_encoder_estimates_callback(const Axis& axis) {
txmsg.isExt = axis.config_.can.is_extended;
txmsg.len = 8;
can_setSignal<float>(txmsg, axis.encoder_.pos_estimate_.any().value_or(0.0f), 0, 32, true);
can_setSignal<float>(txmsg, axis.encoder_.vel_estimate_.any().value_or(0.0f), 32, 32, true);
can_setSignal<float>(txmsg, axis.controller_.pos_estimate_linear_src_.any().value_or(0.0f), 0, 32, true);
can_setSignal<float>(txmsg, axis.controller_.vel_estimate_src_.any().value_or(0.0f), 32, 32, true);
return canbus_->send_message(txmsg);
}
@@ -330,21 +349,40 @@ bool CANSimple::get_iq_callback(const Axis& axis) {
return canbus_->send_message(txmsg);
}
bool CANSimple::get_vbus_voltage_callback(const Axis& axis) {
bool CANSimple::get_bus_voltage_current_callback(const Axis& axis) {
can_Message_t txmsg;
txmsg.id = axis.config_.can.node_id << NUM_CMD_ID_BITS;
txmsg.id += MSG_GET_VBUS_VOLTAGE;
txmsg.id += MSG_GET_BUS_VOLTAGE_CURRENT;
txmsg.isExt = axis.config_.can.is_extended;
txmsg.len = 8;
uint32_t floatBytes;
static_assert(sizeof(vbus_voltage) == sizeof(floatBytes));
static_assert(sizeof(float) == sizeof(vbus_voltage));
static_assert(sizeof(float) == sizeof(ibus_));
can_setSignal<float>(txmsg, vbus_voltage, 0, 32, true);
can_setSignal<float>(txmsg, ibus_, 32, 32, true);
return canbus_->send_message(txmsg);
}
bool CANSimple::get_adc_voltage_callback(const Axis& axis, const can_Message_t& msg) {
can_Message_t txmsg;
txmsg.id = axis.config_.can.node_id << NUM_CMD_ID_BITS;
txmsg.id += MSG_GET_ADC_VOLTAGE;
txmsg.isExt = axis.config_.can.is_extended;
txmsg.len = 8;
auto gpio_num = can_getSignal<uint8_t>(msg, 0, 8, true);
if (gpio_num < GPIO_COUNT) {
auto voltage = get_adc_voltage(get_gpio(gpio_num));
can_setSignal<float>(txmsg, voltage, 0, 32, true);
return canbus_->send_message(txmsg);
} else {
return false;
}
}
void CANSimple::clear_errors_callback(Axis& axis, const can_Message_t& msg) {
odrv.clear_errors(); // TODO: might want to clear axis errors only
}
@@ -361,26 +399,38 @@ uint32_t CANSimple::service_stack() {
}
}
for (auto& a : axes) {
MEASURE_TIME(a.task_times_.can_heartbeat) {
if (a.config_.can.heartbeat_rate_ms > 0) {
if ((now - a.can_.last_heartbeat) >= a.config_.can.heartbeat_rate_ms) {
if (send_heartbeat(a))
a.can_.last_heartbeat = now;
struct periodic {
const uint32_t& rate;
uint32_t& last_time;
bool (CANSimple::* callback)(const Axis& axis);
};
for (auto& axis : axes) {
std::array<periodic, 10> periodics = {{
{axis.config_.can.heartbeat_rate_ms, axis.can_.last_heartbeat, &CANSimple::send_heartbeat},
{axis.config_.can.encoder_rate_ms, axis.can_.last_encoder, &CANSimple::get_encoder_estimates_callback},
{axis.config_.can.motor_error_rate_ms, axis.can_.last_motor_error, &CANSimple::get_motor_error_callback},
{axis.config_.can.encoder_error_rate_ms, axis.can_.last_encoder_error, &CANSimple::get_encoder_error_callback},
{axis.config_.can.controller_error_rate_ms, axis.can_.last_controller_error, &CANSimple::get_controller_error_callback},
{axis.config_.can.sensorless_error_rate_ms, axis.can_.last_sensorless_error, &CANSimple::get_sensorless_error_callback},
{axis.config_.can.encoder_count_rate_ms, axis.can_.last_encoder_count, &CANSimple::get_encoder_count_callback},
{axis.config_.can.iq_rate_ms, axis.can_.last_iq, &CANSimple::get_iq_callback},
{axis.config_.can.sensorless_rate_ms, axis.can_.last_sensorless, &CANSimple::get_sensorless_estimates_callback},
{axis.config_.can.bus_vi_rate_ms, axis.can_.last_bus_vi, &CANSimple::get_bus_voltage_current_callback},
}};
MEASURE_TIME(axis.task_times_.can_heartbeat) {
for (auto& msg : periodics) {
if (msg.rate > 0) {
if ((now - msg.last_time) >= msg.rate) {
if (std::invoke(msg.callback, this, axis)) {
msg.last_time = now;
}
}
int nextAxisService = msg.last_time + msg.rate - now;
nextServiceTime = std::min(nextServiceTime, static_cast<uint32_t>(std::max(0, nextAxisService)));
}
int nextAxisService = a.can_.last_heartbeat + a.config_.can.heartbeat_rate_ms - now;
nextServiceTime = std::min(nextServiceTime, static_cast<uint32_t>(std::max(0, nextAxisService)));
}
if (a.config_.can.encoder_rate_ms > 0) {
if ((now - a.can_.last_encoder) >= a.config_.can.encoder_rate_ms) {
if (get_encoder_estimates_callback(a))
a.can_.last_encoder = now;
}
int nextAxisService = a.can_.last_encoder + a.config_.can.encoder_rate_ms - now;
nextServiceTime = std::min(nextServiceTime, static_cast<uint32_t>(std::max(0, nextAxisService)));
}
}
}
@@ -399,20 +449,19 @@ bool CANSimple::send_heartbeat(const Axis& axis) {
can_setSignal(txmsg, uint8_t(axis.current_state_), 32, 8, true);
// Motor flags
uint8_t motorFlags = 0; // reserved
uint8_t motorFlags = axis.motor_.error_ != 0;
// Encoder flags
uint8_t encoderFlags = 0; // reserved
uint8_t encoderFlags = axis.encoder_.error_ != 0;
// Controller flags
uint8_t controllerFlags = 0;
uint8_t controllerFlags =axis.controller_.error_ != 0;
uint8_t trajDone = uint8_t(axis.controller_.trajectory_done_) << 7;
controllerFlags |= trajDone;
can_setSignal(txmsg, motorFlags, 40, 8, true);
can_setSignal(txmsg, encoderFlags, 48, 8, true);
can_setSignal(txmsg, controllerFlags, 56, 8, true);
// can_setSignal(txmsg, axis.current_state_, 32, 32, true);
return canbus_->send_message(txmsg);
}
+7 -3
View File
@@ -30,11 +30,13 @@ class CANSimple {
MSG_GET_IQ,
MSG_GET_SENSORLESS_ESTIMATES,
MSG_RESET_ODRIVE,
MSG_GET_VBUS_VOLTAGE,
MSG_GET_BUS_VOLTAGE_CURRENT,
MSG_CLEAR_ERRORS,
MSG_SET_LINEAR_COUNT,
MSG_SET_POS_GAIN,
MSG_SET_VEL_GAINS,
MSG_GET_ADC_VOLTAGE,
MSG_GET_CONTROLLER_ERROR,
MSG_CO_HEARTBEAT_CMD = 0x700, // CANOpen NMT Heartbeat SEND
};
@@ -61,7 +63,9 @@ class CANSimple {
bool get_encoder_count_callback(const Axis& axis);
bool get_iq_callback(const Axis& axis);
bool get_sensorless_estimates_callback(const Axis& axis);
bool get_vbus_voltage_callback(const Axis& axis);
bool get_bus_voltage_current_callback(const Axis& axis);
// msg.rtr bit must NOT be set
bool get_adc_voltage_callback(const Axis& axis, const can_Message_t& msg);
// Set functions
static void set_axis_nodeid_callback(Axis& axis, const can_Message_t& msg);
@@ -106,4 +110,4 @@ class CANSimple {
bool extended_node_ids_[AXIS_COUNT];
};
#endif
#endif
+10
View File
@@ -600,6 +600,14 @@ interfaces:
is_extended: bool
heartbeat_rate_ms: uint32
encoder_rate_ms: uint32
motor_error_rate_ms: uint32
encoder_error_rate_ms: uint32
controller_error_rate_ms: uint32
sensorless_error_rate_ms: uint32
encoder_count_rate_ms: uint32
iq_rate_ms: uint32
sensorless_rate_ms: uint32
bus_vi_rate_ms: uint32
ODrive.ThermistorCurrentLimiter:
c_is_class: False
@@ -1188,6 +1196,8 @@ interfaces:
usually corresponds roughly to the current position of the axis.'
}
start_anticogging_calibration:
remove_anticogging_bias: {out: {val: float32}}
get_anticogging_value: {in: {index: uint32}, out: {val: float32}}
ODrive.Encoder:
+4 -1
View File
@@ -1,9 +1,12 @@
# Copy this file to tup.config and adapt it to your needs
# make sure this fits your board
#CONFIG_BOARD_VERSION=v3.5-24V
#CONFIG_BOARD_VERSION=v3.6-56V
CONFIG_DEBUG=false
CONFIG_DOCTEST=false
CONFIG_USE_LTO=false
# Path to the ARM compiler /bin folder (optional)
#CONFIG_ARM_COMPILER_PATH=C:/Tools/ARM/9-2019-q4-major/bin
# Uncomment this to error on compilation warnings
#CONFIG_STRICT=true
+8 -106
View File
@@ -3193,24 +3193,6 @@
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"dev": true
},
"node_modules/app-builder-lib/node_modules/debug": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/app-builder-lib/node_modules/ejs": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.3.tgz",
@@ -4289,24 +4271,6 @@
"node": ">=8.2.5"
}
},
"node_modules/builder-util-runtime/node_modules/debug": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/builder-util/node_modules/ansi-styles": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
@@ -4363,24 +4327,6 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/builder-util/node_modules/debug": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/builder-util/node_modules/fs-extra": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
@@ -10598,15 +10544,15 @@
}
},
"node_modules/jszip": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz",
"integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==",
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"dev": true,
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"set-immediate-shim": "~1.0.1"
"setimmediate": "^1.0.5"
}
},
"node_modules/kew": {
@@ -14512,15 +14458,6 @@
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
},
"node_modules/set-immediate-shim": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
"integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/set-value": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
@@ -21368,15 +21305,6 @@
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"dev": true
},
"debug": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"ejs": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.3.tgz",
@@ -22293,15 +22221,6 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"debug": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"fs-extra": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
@@ -22364,17 +22283,6 @@
"requires": {
"debug": "^4.2.0",
"sax": "^1.2.4"
},
"dependencies": {
"debug": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
}
}
},
"builtin-status-codes": {
@@ -27317,15 +27225,15 @@
}
},
"jszip": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz",
"integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==",
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"dev": true,
"requires": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"set-immediate-shim": "~1.0.1"
"setimmediate": "^1.0.5"
}
},
"kew": {
@@ -30601,12 +30509,6 @@
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
},
"set-immediate-shim": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
"integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=",
"dev": true
},
"set-value": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+2
View File
@@ -10,3 +10,5 @@ To read the voltage on GPIO1 in odrivetool the following would be entered: :code
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.
You may also retrieve voltage measurements from analog inputs via the CAN protocol by sending the Get ADC Voltage message with the GPIO number of the analog input you wish to read. Refer to :ref: `CAN Protocol <can-protocol-doc>` for guidance on how to use the CAN Protocol.
+56 -6
View File
@@ -74,6 +74,7 @@ All multibyte values are little endian (aka Intel format, aka least significant
* 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.
* You must send a valid GPIO pin number in the first byte to recieve coreect ADC voltage feedback. Since you're both sending and receiving data the RTR bit must be set to false.
Cyclic Messages
@@ -82,24 +83,73 @@ Cyclic Messages
Cyclic messages are sent by ODrive on a timer without a request. As of firmware verion `0.5.4`, the Cyclic messsages are:
.. list-table::
:widths: 25 25 50
:widths: 25 25 25 50
:header-rows: 1
* - ID
- Name
- Rate (ms)
* - 0x001
- ODrive Heartbeat Message
- Config Variable
- Default Rate (ms)
* - 0x01
- Heartbeat
- `heartbeat_rate_ms`
- 100
* - 0x009
- Encoder Estimates
* - 0x09
- Get Encoder Estimates
- `encoder_rate_ms`
- 10
* - 0x03
- Get Motor Error
- `motor_error_rate_ms`
- 0
* - 0x04
- Get Encoder Error
- `encoder_error_rate_ms`
- 0
* - 0x1D
- Get Controller Error
- `controller_error_rate_ms`
- 0
* - 0x05
- Get Sensorless Error
- `sensorless_error_rate_ms`
- 0
* - 0x0A
- Get Encoder Count
- `encoder_count_rate_ms`
- 0
* - 0x14
- Get Iq
- `iq_rate_ms`
- 0
* - 0x15
- Get Sensorless Estimates
- `sensorless_rate_ms`
- 0
* - 0x17
- Get Bus Voltage Current
- `bus_vi_rate_ms`
- 0
.. ID | Name | Rate (ms)
.. --: | :-- | :--
.. 0x001 | ODrive Heartbeat Message | 100
.. 0x009 | Encoder Estimates | 10
.. Command ID | Message Name
.. :-- | :-- | :--
.. 0x01 | `heartbeat_rate_ms` | Heartbeat
.. 0x09 | `encoder_rate_ms` | Get Encoder Estimates
.. 0x03 | `motor_error_rate_ms` | Get Motor Error
.. 0x04 | `encoder_error_rate_ms` | Get Encoder Error
.. 0x1D | `controller_error_rate_ms` | Get Controller Error
.. 0x05 | `sensorless_error_rate_ms` | Get Sensorless Error
.. 0x0A | `encoder_count_rate_ms` | Get Encoder Count
.. 0x14 | `iq_rate_ms` | Get Iq
.. 0x15 | `sensorless_rate_ms` | Get Sensorless Estimates
.. 0x17 | `bus_vi_rate_ms` | Get Bus Voltage Current
These can be configured for each axis, see e.g. :code:`axis.config.can`.
+31 -5
View File
@@ -2,16 +2,34 @@ 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
Motor Error Flag
Encoder Error Flag
Controller Error Flag
Trajectory Done Flag","0
4
7","Unsigned Int
5.0
6.0
7.0
7.7","Unsigned Int
Unsigned Int
Bitfield","32
Unsigned Int
Unsigned Int
Unsigned Int
Unsigned Int","32
8
8","-
1
1
1
1","-
-
-
-
-
-","-
-
-
-
-
-"
0x002,ODrive Estop Message,Master,-,-,-,-,-,-
0x003,Get Motor Error*,Axis,Motor Error,0,Unsigned Int,64,1,0
@@ -93,7 +111,13 @@ IEEE 754 Float","32
1","0
0"
0x016,Reboot ODrive,Master***,-,-,-,-,-,-
0x017,Get Vbus Voltage,Master***,Vbus Voltage,0,IEEE 754 Float,32,1,0
0x017,Get Bus Voltage and Current,Master***,"Bus Voltage
Bus Current","0
4","IEEE 754 Float
IEEE 754 Float","32
32","1
1","0
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
@@ -104,4 +128,6 @@ IEEE 754 Float","32
32","1
1","0
0"
0x01C,Get ADC Voltage****,Master***,ADC Voltage,0,IEEE 754 Float,32,1,0
0x01D,Get Controller Error*,Axis,Controller Error,0,Unsigned Int,32,1,0
0x700,CANOpen Heartbeat Message**,Slave,-,-,-,-,-,-
1 CMD ID Name Sender Signals Start byte Signal Type Bits Factor Offset
2 0x000 CANOpen NMT Message** Master - - - - - -
3 0x001 ODrive Heartbeat Message Axis Axis Error Axis Current State Controller Status Axis Error Axis Current State Motor Error Flag Encoder Error Flag Controller Error Flag Trajectory Done Flag 0 4 7 0 4 5.0 6.0 7.0 7.7 Unsigned Int Unsigned Int Bitfield Unsigned Int Unsigned Int Unsigned Int Unsigned Int Unsigned Int Unsigned Int 32 8 8 32 8 1 1 1 1 - - - - - - - - - - - - - - - - - -
4 0x002 ODrive Estop Message Master - - - - - -
5 0x003 Get Motor Error* Axis Motor Error 0 Unsigned Int 64 1 0
6 0x004 Get Encoder Error* Axis Encoder Error 0 Unsigned Int 32 1 0
7 0x005 Get Sensorless Error* Axis Sensorless Error 0 Unsigned Int 32 1 0
8 0x006 Set Axis Node ID Master Axis CAN Node ID 0 Unsigned Int 32 1 0
9 0x004 0x007 Get Encoder Error* Set Axis Requested State Axis Master Encoder Error Axis Requested State 0 Unsigned Int 32 1 0
10 0x005 0x008 Get Sensorless Error* Set Axis Startup Config Axis Master Sensorless Error - Not yet implemented - 0 - Unsigned Int - 32 - 1 - 0 -
11 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
12 0x00A Get Encoder Count* Master Encoder Shadow Count Encoder Count in CPR 0 4 Signed Int Signed Int 32 32 1 1 0 0
13 0x00B Set Controller Modes Master Control Mode Input Mode 0 4 Signed Int Signed Int 32 32 1 1 0 0
14 0x006 0x00C Set Axis Node ID Set Input Pos Master Axis CAN Node ID Input Pos Vel FF Torque FF 0 0 4 6 Unsigned Int IEEE 754 Float Signed Int Signed Int 32 32 16 16 1 1 0.001 0.001 0 0 0 0
15 0x007 0x00D Set Axis Requested State Set Input Vel Master Axis Requested State Input Vel Torque FF 0 0 4 Unsigned Int IEEE 754 Float IEEE 754 Float 32 32 32 1 1 1 0 0 0
16 0x00E Set Input Torque Master Input Torque 0 IEEE 754 Float 32 1 0
17 0x00F Set Limits Master Velocity Limit Current Limit 0 4 IEEE 754 Float IEEE 754 Float 32 1 1 0 0
18 0x010 Start Anticogging Master - - - - - -
19 0x008 0x011 Set Axis Startup Config Set Traj Vel Limit Master - Not yet implemented - Traj Vel Limit - 0 - IEEE 754 Float - 32 - 1 - 0
20 0x009 0x012 Get Encoder Estimates* Set Traj Accel Limits Master Encoder Pos Estimate Encoder Vel Estimate Traj Accel Limit Traj Decel Limit 0 4 IEEE 754 Float IEEE 754 Float 32 32 1 1 0 0
21 0x013 Set Traj Inertia Master Traj Inertia 0 IEEE 754 Float 32 1 0
22 0x014 Get IQ* Axis Iq Setpoint Iq Measured 0 4 IEEE 754 Float IEEE 754 Float 32 32 1 1 0 0
23 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
24 0x016 Reboot ODrive Master*** - - - - - -
25 0x017 Get Bus Voltage and Current Master*** Bus Voltage Bus Current 0 4 IEEE 754 Float IEEE 754 Float 32 32 1 1 0 0
26 0x018 Clear Errors Master - - - - - -
27 0x00A 0x019 Get Encoder Count* Set Linear Count Master Encoder Shadow Count Encoder Count in CPR Position 0 4 0 Signed Int Signed Int Signed Int 32 32 32 1 1 1 0 0 0
28 0x00B 0x01A Set Controller Modes Set Position Gain Master Control Mode Input Mode Pos Gain 0 4 0 Signed Int Signed Int IEEE 754 Float 32 32 32 1 1 1 0 0 0
29 0x00C 0x01B Set Input Pos Set Vel Gains Master Input Pos Vel FF Torque FF Vel Gain Vel Integrator Gain 0 4 6 0 4 IEEE 754 Float Signed Int Signed Int IEEE 754 Float IEEE 754 Float 32 16 16 32 32 1 0.001 0.001 1 1 0 0 0 0 0
30 0x01C Get ADC Voltage**** Master*** ADC Voltage 0 IEEE 754 Float 32 1 0
31 0x01D Get Controller Error* Axis Controller Error 0 Unsigned Int 32 1 0
32 0x700 CANOpen Heartbeat Message** Slave - - - - - -
33
34
35
111
112
113
114
115
116
117
118
119
120
121
122
123
128
129
130
131
132
133
+176 -137
View File
@@ -1,166 +1,184 @@
import cantools
from cantools.database import *
from odrive.enums import *
# 0x00 - NMT Message (Reserved)
msgList = []
nodes = [can.Node('Master')]
buses = [can.Bus('ODrive', None, 100000)]
# 0x001 - Heartbeat
axisError = cantools.database.can.Signal("Axis_Error", 0, 32)
axisState = cantools.database.can.Signal("Axis_State", 32, 8)
motorFlags = cantools.database.can.Signal("Motor_Flags", 40, 8)
encoderFlags = cantools.database.can.Signal("Encoder_Flags", 48, 8)
controllerFlags = cantools.database.can.Signal("Controller_Flags", 56, 8)
for axisID in range(0, 8):
newNode = can.Node(f"ODrive_Axis{axisID}")
nodes.append(newNode)
heartbeatMsg = cantools.database.can.Message(
0x001, "Heartbeat", 8, [axisError, axisState, motorFlags, encoderFlags, controllerFlags]
)
# 0x00 - NMT Message (Reserved)
# 0x002 - E-Stop Message
estopMsg = cantools.database.can.Message(0x002, "Estop", 0, [])
# 0x001 - Heartbeat
axisError = can.Signal("Axis_Error", 0, 32, receivers=['Master'], choices={error.value: error.name for error in AxisError})
axisState = can.Signal("Axis_State", 32, 8, receivers=['Master'], choices={state.value: state.name for state in AxisState})
motorErrorFlag = can.Signal("Motor_Error_Flag", 40, 1, receivers=['Master'])
encoderErrorFlag = can.Signal("Encoder_Error_Flag", 48, 1, receivers=['Master'])
controllerErrorFlag = can.Signal("Controller_Error_Flag", 56, 1, receivers=['Master'])
trajectoryDoneFlag = can.Signal("Trajectory_Done_Flag", 63, 1, receivers=['Master'])
# 0x003 - Motor Error
motorError = cantools.database.can.Signal("Motor_Error", 0, 32)
motorErrorMsg = cantools.database.can.Message(0x003, "Get_Motor_Error", 8, [motorError])
heartbeatMsg = can.Message(
0x001, "Heartbeat", 8,
[
axisError,
axisState,
motorErrorFlag,
encoderErrorFlag,
controllerErrorFlag,
trajectoryDoneFlag
], send_type='cyclic', cycle_time=100, senders=[newNode.name]
)
# 0x004 - Encoder Error
encoderError = cantools.database.can.Signal("Encoder_Error", 0, 32)
encoderErrorMsg = cantools.database.can.Message(
0x004, "Get_Encoder_Error", 8, [encoderError]
)
# 0x002 - E-Stop Message
estopMsg = can.Message(0x002, "Estop", 0, [], senders=['Master'])
# 0x005 - Sensorless Error
sensorlessError = cantools.database.can.Signal("Sensorless_Error", 0, 32)
sensorlessErrorMsg = cantools.database.can.Message(
0x005, "Get_Sensorless_Error", 8, [sensorlessError]
)
# 0x003 - Motor Error
motorError = can.Signal("Motor_Error", 0, 32, receivers=['Master'], choices={error.value: error.name for error in MotorError})
motorErrorMsg = can.Message(0x003, "Get_Motor_Error", 8, [motorError], senders=[newNode.name])
# 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])
# 0x004 - Encoder Error
encoderError = can.Signal("Encoder_Error", 0, 32, receivers=['Master'], choices={error.value: error.name for error in EncoderError})
encoderErrorMsg = can.Message(
0x004, "Get_Encoder_Error", 8, [encoderError], senders=[newNode.name]
)
# 0x007 - Requested State
axisRequestedState = cantools.database.can.Signal("Axis_Requested_State", 0, 32)
setAxisState = cantools.database.can.Message(
0x007, "Set_Axis_State", 8, [axisRequestedState]
)
# 0x005 - Sensorless Error
sensorlessError = can.Signal("Sensorless_Error", 0, 32, receivers=['Master'], choices={error.value: error.name for error in SensorlessEstimatorError})
sensorlessErrorMsg = can.Message(
0x005, "Get_Sensorless_Error", 8, [sensorlessError], senders=[newNode.name]
)
# 0x008 - Startup Config (Reserved)
# 0x006 - Axis Node ID
axisNodeID = can.Signal("Axis_Node_ID", 0, 32, receivers=[newNode.name])
axisNodeMsg = can.Message(0x006, "Set_Axis_Node_ID", 8, [axisNodeID], senders=['Master'])
# 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]
)
# 0x007 - Requested State
axisRequestedState = can.Signal("Axis_Requested_State", 0, 32, receivers=[newNode.name], choices={state.value: state.name for state in AxisState})
setAxisState = can.Message(
0x007, "Set_Axis_State", 8, [axisRequestedState], senders=['Master']
)
# 0x008 - Startup Config (Reserved)
# 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]
)
# 0x009 - Encoder Estimates
encoderPosEstimate = can.Signal("Pos_Estimate", 0, 32, is_float=True, receivers=['Master'], unit='rev')
encoderVelEstimate = can.Signal("Vel_Estimate", 32, 32, is_float=True, receivers=['Master'], unit='rev/s')
encoderEstimates = can.Message(
0x009, "Get_Encoder_Estimates", 8, [encoderPosEstimate, encoderVelEstimate], senders=[newNode.name], send_type='cyclic', cycle_time=10
)
# 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]
)
# 0x00A - Get Encoder Count
encoderShadowCount = can.Signal("Shadow_Count", 0, 32, receivers=['Master'], unit='counts')
encoderCountInCPR = can.Signal("Count_in_CPR", 32, 32, receivers=['Master'], unit='counts')
encoderCountMsg = can.Message(
0x00A, "Get_Encoder_Count", 8, [encoderShadowCount, encoderCountInCPR], senders=[newNode.name]
)
# 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]
)
# 0x00B - Set Controller Modes
controlMode = can.Signal("Control_Mode", 0, 32, receivers=[newNode.name], choices={state.value: state.name for state in ControlMode})
inputMode = can.Signal("Input_Mode", 32, 32, receivers=[newNode.name], choices={state.value: state.name for state in InputMode})
setControllerModeMsg = can.Message(
0x00B, "Set_Controller_Mode", 8, [controlMode, inputMode], senders=['Master']
)
# 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]
)
# 0x00C - Set Input Pos
inputPos = can.Signal("Input_Pos", 0, 32, is_float=True, receivers=[newNode.name], unit='rev')
velFF = can.Signal("Vel_FF", 32, 16, is_signed=True, scale=0.001, receivers=[newNode.name], unit='rev/s')
torqueFF = can.Signal("Torque_FF", 48, 16, is_signed=True, scale=0.001, receivers=[newNode.name], unit='Nm')
setInputPosMsg = can.Message(
0x00C, "Set_Input_Pos", 8, [inputPos, velFF, torqueFF], senders=['Master']
)
# 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]
)
# 0x00D - Set Input Vel
inputVel = can.Signal("Input_Vel", 0, 32, is_float=True, receivers=[newNode.name], unit='rev')
inputTorqueFF = can.Signal("Input_Torque_FF", 32, 32, is_float=True, receivers=[newNode.name], unit='rev/s')
setInputVelMsg = can.Message(
0x00D, "Set_Input_Vel", 8, [inputVel, inputTorqueFF], senders=['Master']
)
# 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]
)
# 0x00E - Set Input Torque
inputTorque = can.Signal("Input_Torque", 0, 32, is_float=True, receivers=[newNode.name], unit='Nm')
setInputTqMsg = can.Message(
0x00E, "Set_Input_Torque", 8, [inputTorque], senders=['Master']
)
# 0x010 - Start Anticogging
startAnticoggingMsg = cantools.database.can.Message(0x010, "Start_Anticogging", 0, [])
# 0x00F - Set Velocity Limit
velLimit = can.Signal("Velocity_Limit", 0, 32, is_float=True, receivers=[newNode.name], unit='rev/s')
currentLimit = can.Signal("Current_Limit", 32, 32, is_float=True, receivers=[newNode.name], unit='A')
setVelLimMsg = can.Message(
0x00F, "Set_Limits", 8, [velLimit, currentLimit], senders=['Master']
)
# 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]
)
# 0x010 - Start Anticogging
startAnticoggingMsg = can.Message(0x010, "Start_Anticogging", 0, [], senders=['Master'])
# 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]
)
# 0x011 - Set Traj Vel Limit
trajVelLim = can.Signal("Traj_Vel_Limit", 0, 32, is_float=True, receivers=[newNode.name], unit='rev/s')
setTrajVelMsg = can.Message(
0x011, "Set_Traj_Vel_Limit", 8, [trajVelLim], senders=['Master']
)
# 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]
)
# 0x012 - Set Traj Accel Limits
trajAccelLim = can.Signal("Traj_Accel_Limit", 0, 32, is_float=True, receivers=[newNode.name], unit='rev/s^2')
trajDecelLim = can.Signal("Traj_Decel_Limit", 32, 32, is_float=True, receivers=[newNode.name], unit='rev/s^2')
setTrajAccelMsg = can.Message(
0x012, "Set_Traj_Accel_Limits", 8, [trajAccelLim, trajDecelLim], senders=['Master']
)
# 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])
# 0x013 - Set Traj Inertia
trajInertia = can.Signal("Traj_Inertia", 0, 32, is_float=True, receivers=[newNode.name], unit='Nm / (rev/s^2)')
trajInertiaMsg = can.Message(
0x013, "Set_Traj_Inertia", 8, [trajInertia], senders=['Master']
)
# 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]
)
# 0x014 - Get Iq
iqSetpoint = can.Signal("Iq_Setpoint", 0, 32, is_float=True, receivers=['Master'], unit='A')
iqMeasured = can.Signal("Iq_Measured", 32, 32, is_float=True, receivers=['Master'], unit='A')
getIqMsg = can.Message(0x014, "Get_Iq", 8, [iqSetpoint, iqMeasured], senders=[newNode.name])
# 0x016 - Reboot ODrive
rebootMsg = cantools.database.can.Message(0x016, "Reboot", 0, [])
# 0x015 - Get Sensorless Estimates
sensorlessPosEstimate = can.Signal("Sensorless_Pos_Estimate", 0, 32, is_float=True, receivers=['Master'], unit='rev')
sensorlessVelEstimate = can.Signal("Sensorless_Vel_Estimate", 32, 32, is_float=True, receivers=['Master'], unit='rev/s')
getSensorlessEstMsg = can.Message(0x015, "Get_Sensorless_Estimates", 8, [sensorlessPosEstimate, sensorlessVelEstimate], senders=[newNode.name])
# 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])
# 0x016 - Reboot ODrive
rebootMsg = can.Message(0x016, "Reboot", 0, [], senders=['Master'])
# 0x018 - Clear Errors
clearErrorsMsg = cantools.database.can.Message(0x018, "Clear_Errors", 0, [])
# 0x017 - Get vbus Voltage and Current
busVoltage = can.Signal("Bus_Voltage", 0, 32, is_float=True, receivers=['Master'], unit='V')
busCurrent = can.Signal("Bus_Current", 32, 32, is_float=True, receivers=['Master'], unit='A')
getVbusVCMsg = can.Message(0x017, "Get_Bus_Voltage_Current", 8, [busVoltage, busCurrent], senders=[newNode.name])
# 0x019 - Set Linear Count
position = cantools.database.can.Signal("Position", 0, 32, is_signed=True)
setLinearCountMsg = cantools.database.can.Message(0x019, "Set_Linear_Count", 8, [position])
# 0x018 - Clear Errors
clearErrorsMsg = can.Message(0x018, "Clear_Errors", 0, [], senders=['Master'])
# 0x01A - Set Pos gain
posGain = cantools.database.can.Signal("Pos_Gain", 0, 32, is_float=True)
setPosGainMsg = cantools.database.can.Message(0x01A, "Set_Pos_Gain", 8, [posGain])
# 0x019 - Set Linear Count
position = can.Signal("Position", 0, 32, is_signed=True, receivers=[newNode.name], unit='counts')
setLinearCountMsg = can.Message(0x019, "Set_Linear_Count", 8, [position], senders=['Master'])
# 0x01B - Set Vel Gains
velGain = cantools.database.can.Signal("Vel_Gain", 0, 32, is_float=True)
velIntGain = cantools.database.can.Signal("Vel_Integrator_Gain", 32, 32, is_float=True)
setVelGainsMsg = cantools.database.can.Message(0x01B, "Set_Vel_gains", 8, [velGain, velIntGain])
# 0x01A - Set Pos gain
posGain = can.Signal("Pos_Gain", 0, 32, is_float=True, receivers=[newNode.name], unit='(rev/s) / rev')
setPosGainMsg = can.Message(0x01A, "Set_Pos_Gain", 8, [posGain], senders=['Master'])
db = cantools.database.can.Database(
[
# 0x01B - Set Vel Gains
velGain = can.Signal("Vel_Gain", 0, 32, is_float=True, receivers=[newNode.name], unit='Nm / (rev/s)')
velIntGain = can.Signal("Vel_Integrator_Gain", 32, 32, is_float=True, receivers=[newNode.name], unit='(Nm / (rev/s)) / s')
setVelGainsMsg = can.Message(0x01B, "Set_Vel_Gains", 8, [velGain, velIntGain], senders=['Master'])
# 0x01C - Get ADC Voltage
adcVoltage = can.Signal("ADC_Voltage", 0, 32, is_float=True, receivers=['Master'], unit='V')
getADCVoltageMsg = can.Message(0x01C, "Get_ADC_Voltage", 8, [adcVoltage], senders=[newNode.name])
# 0x01D - Controller Error
controllerError = can.Signal("Controller_Error", 0, 32, receivers=['Master'], choices={error.value: error.name for error in ControllerError})
controllerErrorMsg = can.Message(
0x01D, "Get_Controller_Error", 8, [controllerError], senders=[newNode.name]
)
axisMsgs = [
heartbeatMsg,
estopMsg,
motorErrorMsg,
encoderErrorMsg,
sensorlessErrorMsg,
@@ -180,14 +198,35 @@ db = cantools.database.can.Database(
getIqMsg,
getSensorlessEstMsg,
rebootMsg,
getVbusVMsg,
getVbusVCMsg,
clearErrorsMsg,
setLinearCountMsg,
setPosGainMsg,
setVelGainsMsg
setVelGainsMsg,
getADCVoltageMsg,
controllerErrorMsg,
]
)
cantools.database.dump_file(db, "odrive-cansimple.dbc")
db = cantools.database.load_file("odrive-cansimple.dbc")
print(db)
masterMsgs = [
estopMsg,
]
# Prepend Axis ID to each message name
for msg in axisMsgs:
msg.name = f"Axis{axisID}_{msg.name}"
msg.frame_id |= (axisID << 5)
# for signal in msg.signals:
# signal.name = f"{signal.name}_Axis{axisID}"
# signal.name = f"Axis{axisID}_{signal.name}"
msgList.append(axisMsgs)
from itertools import chain
msgList = list(chain.from_iterable(msgList))
db = can.Database(msgList, nodes, buses, version='0.5.6')
dump_file(db, "odrive-cansimple.dbc")
db = load_file("odrive-cansimple.dbc")
# print(db)
+10
View File
@@ -3,6 +3,8 @@
# To regenerate this file, nagivate to the top level of the ODrive repository and run:
# python Firmware/interface_generator_stub.py --definitions Firmware/odrive-interface.yaml --template tools/enums_template.j2 --output tools/odrive/enums.py
import enum
[%- for _, enum in value_types.items() %]
[%- if enum.is_enum %]
@@ -13,3 +15,11 @@
[%- endif %]
[%- endfor %]
[%- for _, enum in value_types.items() %]
[%- if enum.is_enum %]
class [[(enum.parent.name if enum.name in ['Error', 'Mode', 'Protocol'] else '') + enum.name]][% if enum.is_flags %](enum.IntFlag)[% else %](enum.Enum)[% endif %]:
[%- for k, value in enum['values'].items() %]
[[((k | to_macro_case)).ljust(40)]] = [% if enum.is_flags %]0x[['%08x' | format(value.value)]][% else %][[value.value]][% endif %]
[%- endfor %]
[%- endif %]
[%- endfor %]
File diff suppressed because it is too large Load Diff
+150
View File
@@ -3,6 +3,8 @@
# To regenerate this file, nagivate to the top level of the ODrive repository and run:
# python Firmware/interface_generator_stub.py --definitions Firmware/odrive-interface.yaml --template tools/enums_template.j2 --output tools/odrive/enums.py
import enum
# ODrive.GpioMode
GPIO_MODE_DIGITAL = 0
GPIO_MODE_DIGITAL_PULL_UP = 1
@@ -165,3 +167,151 @@ ENCODER_ERROR_HALL_NOT_CALIBRATED_YET = 0x00000200
SENSORLESS_ESTIMATOR_ERROR_NONE = 0x00000000
SENSORLESS_ESTIMATOR_ERROR_UNSTABLE_GAIN = 0x00000001
SENSORLESS_ESTIMATOR_ERROR_UNKNOWN_CURRENT_MEASUREMENT = 0x00000002
class GpioMode(enum.Enum):
DIGITAL = 0
DIGITAL_PULL_UP = 1
DIGITAL_PULL_DOWN = 2
ANALOG_IN = 3
UART_A = 4
UART_B = 5
UART_C = 6
CAN_A = 7
I2C_A = 8
SPI_A = 9
PWM = 10
ENC0 = 11
ENC1 = 12
ENC2 = 13
MECH_BRAKE = 14
STATUS = 15
class StreamProtocolType(enum.Enum):
FIBRE = 0
ASCII = 1
STDOUT = 2
ASCII_AND_STDOUT = 3
class CanProtocol(enum.IntFlag):
SIMPLE = 0x00000001
class AxisState(enum.Enum):
UNDEFINED = 0
IDLE = 1
STARTUP_SEQUENCE = 2
FULL_CALIBRATION_SEQUENCE = 3
MOTOR_CALIBRATION = 4
ENCODER_INDEX_SEARCH = 6
ENCODER_OFFSET_CALIBRATION = 7
CLOSED_LOOP_CONTROL = 8
LOCKIN_SPIN = 9
ENCODER_DIR_FIND = 10
HOMING = 11
ENCODER_HALL_POLARITY_CALIBRATION = 12
ENCODER_HALL_PHASE_CALIBRATION = 13
class EncoderMode(enum.Enum):
INCREMENTAL = 0
HALL = 1
SINCOS = 2
SPI_ABS_CUI = 256
SPI_ABS_AMS = 257
SPI_ABS_AEAT = 258
SPI_ABS_RLS = 259
SPI_ABS_MA732 = 260
class ControlMode(enum.Enum):
VOLTAGE_CONTROL = 0
TORQUE_CONTROL = 1
VELOCITY_CONTROL = 2
POSITION_CONTROL = 3
class InputMode(enum.Enum):
INACTIVE = 0
PASSTHROUGH = 1
VEL_RAMP = 2
POS_FILTER = 3
MIX_CHANNELS = 4
TRAP_TRAJ = 5
TORQUE_RAMP = 6
MIRROR = 7
TUNING = 8
class MotorType(enum.Enum):
HIGH_CURRENT = 0
GIMBAL = 2
ACIM = 3
class ODriveError(enum.IntFlag):
NONE = 0x00000000
CONTROL_ITERATION_MISSED = 0x00000001
DC_BUS_UNDER_VOLTAGE = 0x00000002
DC_BUS_OVER_VOLTAGE = 0x00000004
DC_BUS_OVER_REGEN_CURRENT = 0x00000008
DC_BUS_OVER_CURRENT = 0x00000010
BRAKE_DEADTIME_VIOLATION = 0x00000020
BRAKE_DUTY_CYCLE_NAN = 0x00000040
INVALID_BRAKE_RESISTANCE = 0x00000080
class CanError(enum.IntFlag):
NONE = 0x00000000
DUPLICATE_CAN_IDS = 0x00000001
class AxisError(enum.IntFlag):
NONE = 0x00000000
INVALID_STATE = 0x00000001
MOTOR_FAILED = 0x00000040
SENSORLESS_ESTIMATOR_FAILED = 0x00000080
ENCODER_FAILED = 0x00000100
CONTROLLER_FAILED = 0x00000200
WATCHDOG_TIMER_EXPIRED = 0x00000800
MIN_ENDSTOP_PRESSED = 0x00001000
MAX_ENDSTOP_PRESSED = 0x00002000
ESTOP_REQUESTED = 0x00004000
HOMING_WITHOUT_ENDSTOP = 0x00020000
OVER_TEMP = 0x00040000
UNKNOWN_POSITION = 0x00080000
class MotorError(enum.IntFlag):
NONE = 0x00000000
PHASE_RESISTANCE_OUT_OF_RANGE = 0x00000001
PHASE_INDUCTANCE_OUT_OF_RANGE = 0x00000002
DRV_FAULT = 0x00000008
CONTROL_DEADLINE_MISSED = 0x00000010
MODULATION_MAGNITUDE = 0x00000080
CURRENT_SENSE_SATURATION = 0x00000400
CURRENT_LIMIT_VIOLATION = 0x00001000
MODULATION_IS_NAN = 0x00010000
MOTOR_THERMISTOR_OVER_TEMP = 0x00020000
FET_THERMISTOR_OVER_TEMP = 0x00040000
TIMER_UPDATE_MISSED = 0x00080000
CURRENT_MEASUREMENT_UNAVAILABLE = 0x00100000
CONTROLLER_FAILED = 0x00200000
I_BUS_OUT_OF_RANGE = 0x00400000
BRAKE_RESISTOR_DISARMED = 0x00800000
SYSTEM_LEVEL = 0x01000000
BAD_TIMING = 0x02000000
UNKNOWN_PHASE_ESTIMATE = 0x04000000
UNKNOWN_PHASE_VEL = 0x08000000
UNKNOWN_TORQUE = 0x10000000
UNKNOWN_CURRENT_COMMAND = 0x20000000
UNKNOWN_CURRENT_MEASUREMENT = 0x40000000
UNKNOWN_VBUS_VOLTAGE = 0x80000000
UNKNOWN_VOLTAGE_COMMAND = 0x100000000
UNKNOWN_GAINS = 0x200000000
CONTROLLER_INITIALIZING = 0x400000000
UNBALANCED_PHASES = 0x800000000
class ControllerError(enum.IntFlag):
NONE = 0x00000000
OVERSPEED = 0x00000001
INVALID_INPUT_MODE = 0x00000002
UNSTABLE_GAIN = 0x00000004
INVALID_MIRROR_AXIS = 0x00000008
INVALID_LOAD_ENCODER = 0x00000010
INVALID_ESTIMATE = 0x00000020
INVALID_CIRCULAR_RANGE = 0x00000040
SPINOUT_DETECTED = 0x00000080
class EncoderError(enum.IntFlag):
NONE = 0x00000000
UNSTABLE_GAIN = 0x00000001
CPR_POLEPAIRS_MISMATCH = 0x00000002
NO_RESPONSE = 0x00000004
UNSUPPORTED_ENCODER_MODE = 0x00000008
ILLEGAL_HALL_STATE = 0x00000010
INDEX_NOT_FOUND_YET = 0x00000020
ABS_SPI_TIMEOUT = 0x00000040
ABS_SPI_COM_FAIL = 0x00000080
ABS_SPI_NOT_READY = 0x00000100
HALL_NOT_CALIBRATED_YET = 0x00000200
class SensorlessEstimatorError(enum.IntFlag):
NONE = 0x00000000
UNSTABLE_GAIN = 0x00000001
UNKNOWN_CURRENT_MEASUREMENT = 0x00000002