Merge branch 'devel' of https://github.com/madcowswe/ODrive into devel

This commit is contained in:
Oskar Weigl
2019-03-19 20:41:35 -07:00
29 changed files with 228 additions and 169 deletions
+5 -1
View File
@@ -1,6 +1,11 @@
# Unreleased Features
Please add a note of your changes below this heading if you make a Pull Request.
### Added
* Communication watchdog feature.
# Releases
## [0.4.8] - 2019-02-25
### Added
* `dump_errors()` utility function in odrivetool to dump, decode and optionally clear errors.
* `f` command to ascii protocol to get encoder position and velocity feedback.
@@ -19,7 +24,6 @@ Please add a note of your changes below this heading if you make a Pull Request.
### Fixed
* Added required 1.5 cycle phase shift between ADC and PWM, lack thereof caused unstable current controller at high eRPM.
# Releases
## [0.4.7] - 2018-11-28
### Added
* Overspeed fault
+7 -13
View File
@@ -6,6 +6,7 @@
"${workspaceRoot}",
"${workspaceRoot}/fibre/cpp/include/**",
"${workspaceRoot}/MotorControl",
"${workspaceRoot}/communication",
"${workspaceRoot}/Drivers/DRV8301",
"${workspaceRoot}/Board/v3/Inc",
"${workspaceRoot}/Board/v3/Drivers/CMSIS/Include",
@@ -16,30 +17,21 @@
"${workspaceRoot}/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc",
"${workspaceRoot}/Board/v3/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS",
"${workspaceRoot}/Board/v3/Middlewares/Third_Party/FreeRTOS/Source/include",
"${workspaceRoot}/Board/v3/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F",
"${ARM_GCC_ROOT}/arm-none-eabi/include/**",
"${ARM_GCC_ROOT}/lib/gcc/arm-none-eabi/**"
"${workspaceRoot}/Board/v3/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F"
],
"defines": [
"STM32F405xx",
"USE_HAL_DRIVER",
"HW_VERSION_MAJOR=3",
"HW_VERSION_MINOR=5",
"HW_VERSION_VOLTAGE=24",
"HW_VERSION_MINOR=6",
"HW_VERSION_VOLTAGE=56",
"USB_PROTOCOL_NATIVE",
"__weak=\"__attribute__((weak))\"",
"__packed=\"__attribute__((__packed__))\"",
"__GNUC__"
],
"intelliSenseMode": "clang-x64",
"browse": {
"path": [
"${workspaceRoot}",
"${ARM_GCC_ROOT}"
],
"limitSymbolsToIncludedHeaders": true
},
"compilerPath": "${ARM_GCC_ROOT}/bin/arm-none-eabi-gcc.exe -mthumb -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -specs=nosys.specs -specs=nano.specs -u _printf_float -u _scanf_float",
"compilerPath": "\"${ARM_GCC_ROOT}/bin/arm-none-eabi-gcc.exe\" -mthumb -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -specs=nosys.specs -specs=nano.specs -u _printf_float -u _scanf_float",
"cStandard": "c11",
"cppStandard": "c++14"
},
@@ -49,6 +41,7 @@
"${workspaceRoot}",
"${workspaceRoot}/fibre/cpp/include/**",
"${workspaceRoot}/MotorControl",
"${workspaceRoot}/communication",
"${workspaceRoot}/Drivers/DRV8301",
"${workspaceRoot}/Board/v3/Inc",
"${workspaceRoot}/Board/v3/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F",
@@ -92,6 +85,7 @@
"${workspaceRoot}",
"${workspaceRoot}/fibre/cpp/include/**",
"${workspaceRoot}/MotorControl",
"${workspaceRoot}/communication",
"${workspaceRoot}/Drivers/DRV8301",
"${workspaceRoot}/Board/v3/Inc",
"${workspaceRoot}/Board/v3/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F",
+1 -1
View File
@@ -171,7 +171,7 @@
#define CURRENT_MEAS_PERIOD ( (float)2*TIM_1_8_PERIOD_CLOCKS*(TIM_1_8_RCR+1) / (float)TIM_1_8_CLOCK_HZ )
#define CURRENT_MEAS_HZ ( (float)(TIM_1_8_CLOCK_HZ) / (float)(2*TIM_1_8_PERIOD_CLOCKS*(TIM_1_8_RCR+1)) )
#if HW_VERSION_VOLTAGE == 48
#if HW_VERSION_VOLTAGE >= 48
#define VBUS_S_DIVIDER_RATIO 19.0f
#define VBUS_OVERVOLTAGE_LEVEL 52.0f
#elif HW_VERSION_VOLTAGE == 24
+4
View File
@@ -60,6 +60,7 @@
#include "usb_device.h"
extern PCD_HandleTypeDef hpcd_USB_OTG_FS;
int odrive_main(void);
int load_configuration(void);
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
@@ -187,6 +188,9 @@ void MX_FREERTOS_Init(void) {
sem_usb_tx = osSemaphoreCreate(osSemaphore(sem_usb_tx), 1);
init_deferred_interrupts();
// Load persistent configuration (or defaults)
load_configuration();
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
+2 -2
View File
@@ -70,8 +70,8 @@ ifeq ($(OTP_CONFIRM),TRUE)
-c 'mwb 0x1fff7801 0x01' -c 'sleep 10' \
-c 'mwb 0x1fff7802 0x01' -c 'sleep 10' \
-c 'mwb 0x1fff7803 3' -c 'sleep 10' \
-c 'mwb 0x1fff7804 5' -c 'sleep 10' \
-c 'mwb 0x1fff7805 48' -c 'sleep 10' \
-c 'mwb 0x1fff7804 6' -c 'sleep 10' \
-c 'mwb 0x1fff7805 56' -c 'sleep 10' \
-c 'reset run' \
-c exit
@echo "OK"
+36
View File
@@ -28,6 +28,7 @@ Axis::Axis(const AxisHardwareConfig_t& hw_config,
trap_.axis_ = this;
decode_step_dir_pins();
update_watchdog_settings();
}
static void step_cb_wrapper(void* ctx) {
@@ -88,6 +89,21 @@ void Axis::decode_step_dir_pins() {
dir_pin_ = get_gpio_pin_by_pin(config_.dir_gpio_pin);
}
// @brief: Setup the watchdog reset value from the configuration watchdog timeout interval.
void Axis::update_watchdog_settings() {
if(config_.watchdog_timeout <= 0.0f) { // watchdog disabled
watchdog_reset_value_ = 0;
} else if(config_.watchdog_timeout >= UINT32_MAX / (current_meas_hz+1)) { //overflow!
watchdog_reset_value_ = UINT32_MAX;
} else {
watchdog_reset_value_ = static_cast<uint32_t>(config_.watchdog_timeout * current_meas_hz);
}
// Do a feed to avoid instant timeout
watchdog_feed();
}
// @brief (de)activates step/dir input
void Axis::set_step_dir_active(bool active) {
if (active) {
@@ -141,6 +157,26 @@ bool Axis::do_updates() {
return check_for_errors();
}
// @brief Feed the watchdog to prevent watchdog timeouts.
void Axis::watchdog_feed() {
watchdog_current_value_ = watchdog_reset_value_;
}
// @brief Check the watchdog timer for expiration. Also sets the watchdog error bit if expired.
bool Axis::watchdog_check() {
// reset value = 0 means watchdog disabled.
if(watchdog_reset_value_ == 0) return true;
// explicit check here to ensure that we don't underflow back to UINT32_MAX
if(watchdog_current_value_ > 0) {
watchdog_current_value_--;
return true;
} else {
error_ |= ERROR_WATCHDOG_TIMER_EXPIRED;
return false;
}
}
bool Axis::run_lockin_spin() {
// Spiral up current for softer rotor lock-in
lockin_state_ = LOCKIN_STATE_RAMP;
+21 -2
View File
@@ -20,6 +20,7 @@ public:
ERROR_ENCODER_FAILED = 0x100, // Go to encoder.hpp for information, check odrvX.axisX.encoder.error for error value
ERROR_CONTROLLER_FAILED = 0x200,
ERROR_POS_CTRL_DURING_SENSORLESS = 0x400,
ERROR_WATCHDOG_TIMER_EXPIRED = 0x800,
};
enum State_t {
@@ -59,6 +60,8 @@ public:
// For M0 this has no effect if enable_uart is true
float counts_per_step = 2.0f;
float watchdog_timeout = 0.0f; // [s] (0 disables watchdog)
// Defaults loaded from hw_config in load_configuration in main.cpp
uint16_t step_gpio_pin = 0;
uint16_t dir_gpio_pin = 0;
@@ -93,6 +96,8 @@ public:
void step_cb();
void set_step_dir_active(bool enable);
void decode_step_dir_pins();
void update_watchdog_settings();
static void load_default_step_dir_pin_config(
const AxisHardwareConfig_t& hw_config, Config_t* config);
@@ -101,6 +106,10 @@ public:
bool do_checks();
bool do_updates();
void watchdog_feed();
bool watchdog_check();
// True if there are no errors
bool inline check_for_errors() {
return error_ == ERROR_NONE;
@@ -134,8 +143,11 @@ public:
// Update all estimators
// Note: updates run even if checks fail
bool updates_ok = do_updates();
// make sure the watchdog is being fed.
bool watchdog_ok = watchdog_check();
if (!checks_ok || !updates_ok) {
if (!checks_ok || !updates_ok || !watchdog_ok) {
// It's not useful to quit idle since that is the safe action
// Also leaving idle would rearm the motors
if (current_state_ != AXIS_STATE_IDLE)
@@ -199,6 +211,10 @@ public:
uint32_t loop_counter_ = 0;
LockinState_t lockin_state_ = LOCKIN_STATE_INACTIVE;
// watchdog
uint32_t watchdog_reset_value_ = 0; //computed from config_.watchdog_timeout in update_watchdog_settings()
uint32_t watchdog_current_value_= 0;
// Communication protocol definitions
auto make_protocol_definitions() {
return make_protocol_member_list(
@@ -216,6 +232,8 @@ public:
make_protocol_property("startup_sensorless_control", &config_.startup_sensorless_control),
make_protocol_property("enable_step_dir", &config_.enable_step_dir),
make_protocol_property("counts_per_step", &config_.counts_per_step),
make_protocol_property("watchdog_timeout", &config_.watchdog_timeout,
[](void* ctx) { static_cast<Axis*>(ctx)->update_watchdog_settings(); }, this),
make_protocol_property("step_gpio_pin", &config_.step_gpio_pin,
[](void* ctx) { static_cast<Axis*>(ctx)->decode_step_dir_pins(); }, this),
make_protocol_property("dir_gpio_pin", &config_.dir_gpio_pin,
@@ -236,7 +254,8 @@ public:
make_protocol_object("controller", controller_.make_protocol_definitions()),
make_protocol_object("encoder", encoder_.make_protocol_definitions()),
make_protocol_object("sensorless_estimator", sensorless_estimator_.make_protocol_definitions()),
make_protocol_object("trap_traj", trap_.make_protocol_definitions())
make_protocol_object("trap_traj", trap_.make_protocol_definitions()),
make_protocol_function("watchdog_feed", *this, &Axis::watchdog_feed)
);
}
};
+1 -1
View File
@@ -34,7 +34,7 @@ public:
bool setpoints_in_cpr = false;
};
Controller(Config_t& config);
explicit Controller(Config_t& config);
void reset();
void set_error(Error_t error);
+18 -12
View File
@@ -39,9 +39,8 @@ bool Encoder::do_checks(){
// Triggered when an encoder passes over the "Index" pin
// TODO: only arm index edge interrupt when we know encoder has powered up
// (maybe by attaching the interrupt on start search, synergistic with following)
// TODO: disable interrupt once we found the index
void Encoder::enc_index_cb() {
if (config_.use_index && !index_found_) {
if (config_.use_index) {
set_circular_count(0, false);
if (config_.zero_count_on_find_idx)
set_linear_count(0); // Avoid position control transient after search
@@ -71,6 +70,23 @@ void Encoder::set_idx_subscribe(bool override_enable) {
}
}
void Encoder::update_pll_gains() {
pll_kp_ = 2.0f * config_.bandwidth; // basic conversion to discrete time
pll_ki_ = 0.25f * (pll_kp_ * pll_kp_); // Critically damped
// Check that we don't get problems with discrete time approximation
if (!(current_meas_period * pll_kp_ < 1.0f)) {
set_error(ERROR_UNSTABLE_GAIN);
}
}
void Encoder::check_pre_calibrated() {
if (!is_ready_)
config_.pre_calibrated = false;
if (config_.mode == MODE_INCREMENTAL && !index_found_)
config_.pre_calibrated = false;
}
// Function that sets the current encoder count to a desired 32-bit value.
void Encoder::set_linear_count(int32_t count) {
// Disable interrupts to make a critical section to avoid race condition
@@ -261,16 +277,6 @@ static bool decode_hall(uint8_t hall_state, int32_t* hall_cnt) {
}
}
void Encoder::update_pll_gains() {
pll_kp_ = 2.0f * config_.bandwidth; // basic conversion to discrete time
pll_ki_ = 0.25f * (pll_kp_ * pll_kp_); // Critically damped
// Check that we don't get problems with discrete time approximation
if (!(current_meas_period * pll_kp_ < 1.0f)) {
set_error(ERROR_UNSTABLE_GAIN);
}
}
void Encoder::sample_now() {
switch (config_.mode) {
case MODE_INCREMENTAL: {
+7 -4
View File
@@ -52,6 +52,8 @@ public:
void enc_index_cb();
void set_idx_subscribe(bool override_enable = false);
void update_pll_gains();
void check_pre_calibrated();
void set_linear_count(int32_t count);
void set_circular_count(int32_t count, bool update_offset);
@@ -63,7 +65,7 @@ public:
void sample_now();
bool update();
void update_pll_gains();
const EncoderHardwareConfig_t& hw_config_;
Config_t& config_;
@@ -92,8 +94,8 @@ public:
auto make_protocol_definitions() {
return make_protocol_member_list(
make_protocol_property("error", &error_),
make_protocol_ro_property("is_ready", &is_ready_),
make_protocol_ro_property("index_found", const_cast<bool*>(&index_found_)),
make_protocol_property("is_ready", &is_ready_),
make_protocol_property("index_found", const_cast<bool*>(&index_found_)),
make_protocol_property("shadow_count", &shadow_count_),
make_protocol_property("count_in_cpr", &count_in_cpr_),
make_protocol_property("interpolation", &interpolation_),
@@ -110,7 +112,8 @@ public:
[](void* ctx) { static_cast<Encoder*>(ctx)->set_idx_subscribe(); }, this),
make_protocol_property("find_idx_on_lockin_only", &config_.find_idx_on_lockin_only,
[](void* ctx) { static_cast<Encoder*>(ctx)->set_idx_subscribe(); }, this),
make_protocol_property("pre_calibrated", &config_.pre_calibrated),
make_protocol_property("pre_calibrated", &config_.pre_calibrated,
[](void* ctx) { static_cast<Encoder*>(ctx)->check_pre_calibrated(); }, this),
make_protocol_property("zero_count_on_find_idx", &config_.zero_count_on_find_idx),
make_protocol_property("cpr", &config_.cpr),
make_protocol_property("offset", &config_.offset),
+2 -3
View File
@@ -45,7 +45,7 @@ void save_configuration(void) {
}
}
void load_configuration(void) {
extern "C" int load_configuration(void) {
// Try to load configs
if (NVM_init() ||
ConfigFormat::safe_load_config(
@@ -71,6 +71,7 @@ void load_configuration(void) {
} else {
user_config_loaded_ = true;
}
return user_config_loaded_;
}
void erase_configuration(void) {
@@ -116,8 +117,6 @@ void vApplicationIdleHook(void) {
}
int odrive_main(void) {
// Load persistent configuration (or defaults)
load_configuration();
#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3
if (board_config.enable_i2c_instead_of_can) {
+1 -1
View File
@@ -77,7 +77,7 @@ struct BoardConfig_t {
float brake_resistance = 0.47f; // [ohm]
#endif
float dc_bus_undervoltage_trip_level = 8.0f; //<! [V] minimum voltage below which the motor stops operating
float dc_bus_overvoltage_trip_level = 1.08f * HW_VERSION_VOLTAGE; //<! [V] maximum voltage above which the motor stops operating.
float dc_bus_overvoltage_trip_level = 1.07f * HW_VERSION_VOLTAGE; //<! [V] maximum voltage above which the motor stops operating.
//<! This protects against cases in which the power supply fails to dissipate
//<! the brake power if the brake resistor is disabled.
//<! The default is 26V for the 24V board version and 52V for the 48V board version.
@@ -14,7 +14,7 @@ public:
float pm_flux_linkage = 1.58e-3f; // [V / (rad/s)] { 5.51328895422 / (<pole pairs> * <rpm/v>) }
};
SensorlessEstimator(Config_t& config);
explicit SensorlessEstimator(Config_t& config);
bool update();
+2 -1
View File
@@ -9,13 +9,14 @@ public:
float decel_limit = 5000.0f; // [count/s^2]
float A_per_css = 0.0f; // [A/(count/s^2)]
};
struct Step_t {
float Y;
float Yd;
float Ydd;
};
TrapezoidalTrajectory(Config_t& config);
explicit TrapezoidalTrajectory(Config_t& config);
bool planTrapezoidal(float Xf, float Xi, float Vi,
float Vmax, float Amax, float Dmax);
Step_t eval(float t);
+15 -6
View File
@@ -31,6 +31,14 @@ elseif boardversion == "v3.5-48V" then
boarddir = 'Board/v3'
FLAGS += "-DHW_VERSION_MAJOR=3 -DHW_VERSION_MINOR=5"
FLAGS += "-DHW_VERSION_VOLTAGE=48"
elseif boardversion == "v3.6-24V" then
boarddir = 'Board/v3'
FLAGS += "-DHW_VERSION_MAJOR=3 -DHW_VERSION_MINOR=6"
FLAGS += "-DHW_VERSION_VOLTAGE=24"
elseif boardversion == "v3.6-56V" then
boarddir = 'Board/v3'
FLAGS += "-DHW_VERSION_MAJOR=3 -DHW_VERSION_MINOR=6"
FLAGS += "-DHW_VERSION_VOLTAGE=56"
elseif boardversion == "" then
error("board version not specified - take a look at tup.config.default")
else
@@ -91,10 +99,6 @@ FLAGS += '-mfpu=fpv4-sp-d16'
FLAGS += '-mfloat-abi=hard'
FLAGS += { '-Wall', '-Wdouble-promotion', '-Wfloat-conversion', '-fdata-sections', '-ffunction-sections'}
-- debug build
FLAGS += '-g -gdwarf-2'
-- linker flags
LDFLAGS += '-T'..boarddir..'/STM32F405RGTx_FLASH.ld'
LDFLAGS += '-L'..boarddir..'/Drivers/CMSIS/Lib' -- lib dir
@@ -102,10 +106,15 @@ LDFLAGS += '-lc -lm -lnosys -larm_cortexM4lf_math' -- libs
LDFLAGS += '-mthumb -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -specs=nosys.specs -specs=nano.specs -u _printf_float -u _scanf_float -Wl,--cref -Wl,--gc-sections'
LDFLAGS += '-Wl,--undefined=uxTopUsedPriority'
-- debug build
if tup.getconfig("DEBUG") == "true" then
FLAGS += '-g -gdwarf-2'
OPT += '-Og'
else
OPT += '-O2'
end
-- common flags for ASM, C and C++
OPT += '-Og'
-- OPT += '-O0'
OPT += '-ffast-math -fno-finite-math-only'
tup.append_table(FLAGS, OPT)
tup.append_table(LDFLAGS, OPT)
+3 -3
View File
@@ -56,14 +56,14 @@ function GCCToolchain(prefix, builddir, compiler_flags, linker_flags)
compiler_flags += '-fstack-usage'
end
gcc_generic_compiler = function(compiler, compiler_flags, gen_su_file, src, flags, includes, outputs)
local gcc_generic_compiler = function(compiler, compiler_flags, gen_su_file, src, flags, includes, outputs)
-- convert include list to flags
inc_flags = {}
for _,inc in pairs(includes) do
inc_flags += "-I"..inc
end
-- todo: vary build directory
obj_file = builddir.."/"..src:gsub("/","_")..".o"
obj_file = builddir.."/obj/"..src:gsub("/","_")..".o"
outputs.object_files += obj_file
if gen_su_file then
su_file = builddir.."/"..src:gsub("/","_")..".su"
@@ -162,7 +162,7 @@ function build(args)
outputs.includes = {}
for _,inc in pairs(args.includes) do
table.insert(outputs.includes, tup.nodevariable(inc))
table.insert(outputs.includes, inc)
end
if args.name != nil then
all_packages[args.name] = outputs
+25 -4
View File
@@ -99,7 +99,9 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
vel_feed_forward = 0.0f;
if (numscan < 4)
current_feed_forward = 0.0f;
axes[motor_number]->controller_.set_pos_setpoint(pos_setpoint, vel_feed_forward, current_feed_forward);
Axis* axis = axes[motor_number];
axis->controller_.set_pos_setpoint(pos_setpoint, vel_feed_forward, current_feed_forward);
axis->watchdog_feed();
}
} else if (cmd[0] == 'q') { // position control with limits
@@ -117,6 +119,8 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
axis->controller_.config_.vel_limit = vel_limit;
if (numscan >= 4)
axis->motor_.config_.current_lim = current_lim;
axis->watchdog_feed();
}
} else if (cmd[0] == 'v') { // velocity control
@@ -130,7 +134,9 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
} else {
if (numscan < 3)
current_feed_forward = 0.0f;
axes[motor_number]->controller_.set_vel_setpoint(vel_setpoint, current_feed_forward);
Axis* axis = axes[motor_number];
axis->controller_.set_vel_setpoint(vel_setpoint, current_feed_forward);
axis->watchdog_feed();
}
} else if (cmd[0] == 'c') { // current control
@@ -142,7 +148,9 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
} else if (motor_number >= AXIS_COUNT) {
respond(response_channel, use_checksum, "invalid motor %u", motor_number);
} else {
axes[motor_number]->controller_.set_current_setpoint(current_setpoint);
Axis* axis = axes[motor_number];
axis->controller_.set_current_setpoint(current_setpoint);
axis->watchdog_feed();
}
} else if (cmd[0] == 't') { // trapezoidal trajectory
@@ -154,7 +162,9 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
} else if (motor_number >= AXIS_COUNT) {
respond(response_channel, use_checksum, "invalid motor %u", motor_number);
} else {
axes[motor_number]->controller_.move_to_pos(goal_point);
Axis* axis = axes[motor_number];
axis->controller_.move_to_pos(goal_point);
axis->watchdog_feed();
}
} else if (cmd[0] == 'f') { // feedback
@@ -240,6 +250,17 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
}
}
}else if (cmd[0] == 'u') { // Update axis watchdog.
unsigned motor_number;
int numscan = sscanf(cmd, "u %u", &motor_number);
if(numscan < 1){
respond(response_channel, use_checksum, "invalid command format");
} else if (motor_number >= AXIS_COUNT) {
respond(response_channel, use_checksum, "invalid motor %u", motor_number);
}else {
axes[motor_number]->watchdog_feed();
}
} else if (cmd[0] != 0) {
respond(response_channel, use_checksum, "unknown command");
}
+22 -104
View File
@@ -82,61 +82,19 @@ typedef struct {
uint16_t endpoint_id;
} endpoint_ref_t;
#include <cstring>
template<typename T, typename = typename std::enable_if_t<!std::is_const<T>::value>>
inline size_t write_le(T value, uint8_t* buffer);
inline size_t write_le(T value, uint8_t* buffer){
//TODO: add static_assert that this is still a little endian machine
std::memcpy(&buffer[0], &value, sizeof(value));
return sizeof(value);
}
template<typename T>
inline size_t read_le(T* value, const uint8_t* buffer);
template<>
inline size_t write_le<bool>(bool value, uint8_t* buffer) {
buffer[0] = value ? 1 : 0;
return 1;
}
template<>
inline size_t write_le<uint8_t>(uint8_t value, uint8_t* buffer) {
buffer[0] = value;
return 1;
}
template<>
inline size_t write_le<uint16_t>(uint16_t value, uint8_t* buffer) {
buffer[0] = (value >> 0) & 0xff;
buffer[1] = (value >> 8) & 0xff;
return 2;
}
template<>
inline size_t write_le<uint32_t>(uint32_t value, uint8_t* buffer) {
buffer[0] = (value >> 0) & 0xff;
buffer[1] = (value >> 8) & 0xff;
buffer[2] = (value >> 16) & 0xff;
buffer[3] = (value >> 24) & 0xff;
return 4;
}
template<>
inline size_t write_le<int32_t>(int32_t value, uint8_t* buffer) {
buffer[0] = (value >> 0) & 0xff;
buffer[1] = (value >> 8) & 0xff;
buffer[2] = (value >> 16) & 0xff;
buffer[3] = (value >> 24) & 0xff;
return 4;
}
template<>
inline size_t write_le<uint64_t>(uint64_t value, uint8_t* buffer) {
buffer[0] = (value >> 0) & 0xff;
buffer[1] = (value >> 8) & 0xff;
buffer[2] = (value >> 16) & 0xff;
buffer[3] = (value >> 24) & 0xff;
buffer[4] = (value >> 32) & 0xff;
buffer[5] = (value >> 40) & 0xff;
buffer[6] = (value >> 48) & 0xff;
buffer[7] = (value >> 56) & 0xff;
return 8;
typename std::enable_if_t<std::is_const<T>::value, size_t>
write_le(T value, uint8_t* buffer) {
return write_le<std::remove_const_t<T>>(value, buffer);
}
template<>
@@ -148,65 +106,17 @@ inline size_t write_le<float>(float value, uint8_t* buffer) {
}
template<typename T>
typename std::enable_if_t<std::is_const<T>::value, size_t>
write_le(T value, uint8_t* buffer) {
return write_le<std::remove_const_t<T>>(value, buffer);
}
template<>
inline size_t read_le<bool>(bool* value, const uint8_t* buffer) {
*value = buffer[0];
return 1;
}
template<>
inline size_t read_le<uint8_t>(uint8_t* value, const uint8_t* buffer) {
*value = buffer[0];
return 1;
}
template<>
inline size_t read_le<uint16_t>(uint16_t* value, const uint8_t* buffer) {
*value = (static_cast<uint16_t>(buffer[0]) << 0) |
(static_cast<uint16_t>(buffer[1]) << 8);
return 2;
}
template<>
inline size_t read_le<int32_t>(int32_t* value, const uint8_t* buffer) {
*value = (static_cast<int32_t>(buffer[0]) << 0) |
(static_cast<int32_t>(buffer[1]) << 8) |
(static_cast<int32_t>(buffer[2]) << 16) |
(static_cast<int32_t>(buffer[3]) << 24);
return 4;
}
template<>
inline size_t read_le<uint32_t>(uint32_t* value, const uint8_t* buffer) {
*value = (static_cast<uint32_t>(buffer[0]) << 0) |
(static_cast<uint32_t>(buffer[1]) << 8) |
(static_cast<uint32_t>(buffer[2]) << 16) |
(static_cast<uint32_t>(buffer[3]) << 24);
return 4;
}
template<>
inline size_t read_le<uint64_t>(uint64_t* value, const uint8_t* buffer) {
*value = (static_cast<uint64_t>(buffer[0]) << 0) |
(static_cast<uint64_t>(buffer[1]) << 8) |
(static_cast<uint64_t>(buffer[2]) << 16) |
(static_cast<uint64_t>(buffer[3]) << 24) |
(static_cast<uint64_t>(buffer[4]) << 32) |
(static_cast<uint64_t>(buffer[5]) << 40) |
(static_cast<uint64_t>(buffer[6]) << 48) |
(static_cast<uint64_t>(buffer[7]) << 56);
return 8;
inline size_t read_le(T* value, const uint8_t* buffer){
// TODO: add static_assert that this is still a little endian machine
std::memcpy(value, buffer, sizeof(*value));
return sizeof(*value);
}
template<>
inline size_t read_le<float>(float* value, const uint8_t* buffer) {
static_assert(CHAR_BIT * sizeof(float) == 32, "32 bit floating point expected");
static_assert(std::numeric_limits<float>::is_iec559, "IEEE 754 floating point expected");
return read_le(reinterpret_cast<uint32_t*>(value), buffer);
}
@@ -499,6 +409,14 @@ inline constexpr const char* get_default_json_modifier<float>() {
return "\"type\":\"float\",\"access\":\"rw\"";
}
template<>
inline constexpr const char* get_default_json_modifier<const int64_t>() {
return "\"type\":\"int64\",\"access\":\"r\"";
}
template<>
inline constexpr const char* get_default_json_modifier<int64_t>() {
return "\"type\":\"int64\",\"access\":\"rw\"";
}
template<>
inline constexpr const char* get_default_json_modifier<const uint64_t>() {
return "\"type\":\"uint64\",\"access\":\"r\"";
}
+1 -1
View File
@@ -5,7 +5,7 @@ function GCCToolchain(prefix, builddir, compiler_flags, linker_flags)
-- add some default compiler flags
compiler_flags += '-fstack-usage'
gcc_generic_compiler = function(compiler, compiler_flags, gen_su_file, src, flags, includes, outputs)
local gcc_generic_compiler = function(compiler, compiler_flags, gen_su_file, src, flags, includes, outputs)
-- resolve source path
src = tostring(src)
+1
View File
@@ -3,6 +3,7 @@
#CONFIG_BOARD_VERSION=v3.5-24V
CONFIG_USB_PROTOCOL=native
CONFIG_UART_PROTOCOL=ascii
CONFIG_DEBUG=false
# Uncomment this to error on compilation warnings
#CONFIG_STRICT=true
+10
View File
@@ -11,6 +11,16 @@
}
],
"settings": {
"c-cpp-flylint.cppcheck.includePaths": [
"${workspaceRoot}",
"${workspaceRoot}/fibre/cpp/include/fibre",
"${workspaceRoot}/communication",
"${workspaceRoot}/MotorControl",
],
"c-cpp-flylint.cppcheck.platform": "avr8",
"c-cpp-flylint.cppcheck.standard": ["c99","c++14"],
"files.associations": {
"memory": "cpp",
"utility": "cpp",
+3 -3
View File
@@ -70,7 +70,7 @@ GEM
listen (= 3.1.5)
mercenary (~> 0.3)
minima (= 2.4.0)
nokogiri (>= 1.8.1, < 2.0)
nokogiri (>= 1.8.5, < 2.0)
rouge (= 2.2.1)
terminal-table (~> 1.4)
github-pages-health-check (1.4.0)
@@ -81,7 +81,7 @@ GEM
typhoeus (~> 1.3)
html-pipeline (2.7.1)
activesupport (>= 2)
nokogiri (>= 1.4)
nokogiri (>= 1.8.5)
http_parser.rb (0.6.0)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
@@ -207,7 +207,7 @@ GEM
minitest (5.11.3)
multipart-post (2.0.0)
net-dns (0.8.0)
nokogiri (1.8.2)
nokogiri (>= 1.8.5)
mini_portile2 (~> 2.3.0)
octokit (4.8.0)
sawyer (~> 0.8.0, >= 0.5.3)
+18
View File
@@ -36,6 +36,8 @@ Example: `t 0 -20000`
For general moving around of the axis, this is the recommended command.
This command updates the watchdog timer for the motor.
#### Motor Position command
For basic use where you send one setpoint at at a time, use the `q` command.
If you have a realtime controller that is streaming setpoints and tracking a trajectory, use the `p` command.
@@ -64,6 +66,7 @@ Example: `p 0 -20000 0 0`
Note that if you don't know what feed-forward is or what it's used for, simply omit it.
This command updates the watchdog timer for the motor.
#### Motor Velocity command
```
@@ -78,6 +81,8 @@ Example: `v 0 1000 0`
Note that if you don't know what feed-forward is or what it's used for, simply omit it.
This command updates the watchdog timer for the motor.
#### Motor Current command
```
c motor current
@@ -86,6 +91,19 @@ c motor current
* `motor` is the motor number, `0` or `1`.
* `current` is the desired current in A.
This command updates the watchdog timer for the motor.
#### Update motor watchdog
```
u motor
```
* `u` for /u/pdate.
* `motor` is the motor number, `0` or `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.
+2 -1
View File
@@ -70,7 +70,8 @@ sudo pacman -S tup
#### Mac
First install [Homebrew](https://brew.sh/). Then you can run these commands in Terminal:
```bash
brew cask install gcc-arm-embedded
brew tap osx-cross/arm
brew install arm-gcc-bin
brew cask install osxfuse && brew install tup
brew install openocd
```
+12
View File
@@ -331,6 +331,18 @@ You can now control the current with `axis.controller.current_setpoint = 3` [A].
*Note: There is no velocity limiting in current control mode. Make sure that you don't overrev the motor, or exceed the max speed for your encoder.*
## Watchdog Timer
Each axis has a configurable watchdog timer that can stop the motors if the
control connection to the ODrive is interrupted.
Each axis has a configurable watchdog timeout: `axis.config.watchdog_timeout`,
measured in seconds. A value of `0` disables the watchdog functionality. Any value
`> 0` will stop the motors if the watchdog has not been fed in the configured
time interval.
The watchdog is fed using the `axis.watchdog_feed()` method of each axis.
## What's next?
You can now:
* See what other [commands and parameters](commands.md) are available, including setting tuning parameters for better performance.
+1
View File
@@ -116,6 +116,7 @@ You can use the DfuSe app from ST.
1. Force the ODrive into DFU mode, as per the instructions above "How to force DFU mode".
1. In the top left it should now be connected to "STM Device in DFU Mode".
1. 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).
2. 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 `C:\Windows\System32\drivers\libusb0.sys`.
1. In the bottom right section called "Upgrade or Verify Action" click the button "Choose...".
1. Locate the `ODriveFirmware.dfu` we made before.
1. Click button "Upgrade".
+2 -2
View File
@@ -37,9 +37,9 @@ You can monitor your PUS voltage using liveplotter in odrive tool by entering `s
* `ERROR_DC_BUS_OVER_VOLTAGE = 0x04`
Confirm that you have a break resistor of the correct value connected securly and that `odrv0.config.brake_resistance` is set to the value of your break resistor.
Confirm that you have a brake resistor of the correct value connected securly and that `odrv0.config.brake_resistance` is set to the value of your brake resistor.
You can monitor your PUS voltage using liveplotter in odrive tool by entering `start_liveplotter(lambda: [odrv0.vbus_voltage])`. If during a move you see the voltage rise above your PSU's nominal set voltage then you have your break resistance set too low. This may happen if you are using long wires or small gauge wires to connect your break resistor to your odrive which will added extra resistance. This extra resistance needs to be accounted for to prevent this voltage spike. If you have checked all your connections you can also try increasing your break resistance by ~ 0.01 Ohm at a time to a maximum of 0.05 greater than your break resistor value.
You can monitor your PUS voltage using liveplotter in odrive tool by entering `start_liveplotter(lambda: [odrv0.vbus_voltage])`. If during a move you see the voltage rise above your PSU's nominal set voltage then you have your brake resistance set too low. This may happen if you are using long wires or small gauge wires to connect your brake resistor to your odrive which will added extra resistance. This extra resistance needs to be accounted for to prevent this voltage spike. If you have checked all your connections you can also try increasing your brake resistance by ~ 0.01 Ohm at a time to a maximum of 0.05 greater than your brake resistor value.
## Common Motor Errors
+1
View File
@@ -27,6 +27,7 @@ class errors:
ERROR_ENCODER_FAILED = 0x100 # Go to encoder.hpp for information, check odrvX.axisX.encoder.error for error value
ERROR_CONTROLLER_FAILED = 0x200
ERROR_POS_CTRL_DURING_SENSORLESS = 0x400
ERROR_WATCHDOG_TIMER_EXPIRED = 0x800
class motor:
ERROR_NONE = 0
+4 -3
View File
@@ -32,9 +32,10 @@ class OperationAbortedException(Exception):
pass
def dump_errors(odrv, clear=False):
axes = [axis for name, axis in odrv._remote_attributes.items() if 'axis' in name]
for num, axis in enumerate(axes):
print('Axis{}:'.format(num))
axes = [(name, axis) for name, axis in odrv._remote_attributes.items() if 'axis' in name]
axes.sort()
for name, axis in axes:
print(name)
# Flatten axis and submodules
# (name, remote_obj, errorcode)