mirror of
https://github.com/odriverobotics/ODrive.git
synced 2026-09-26 12:27:45 +08:00
Merge branch 'devel' of https://github.com/madcowswe/ODrive into devel
This commit is contained in:
+5
-1
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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\"";
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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".
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user