diff --git a/CHANGELOG.md b/CHANGELOG.md index 85831d9a..e0d2f219 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index 570c0f29..c4a2f37c 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -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", diff --git a/Firmware/Board/v3/Inc/main.h b/Firmware/Board/v3/Inc/main.h index d41b19ee..bfd9888c 100644 --- a/Firmware/Board/v3/Inc/main.h +++ b/Firmware/Board/v3/Inc/main.h @@ -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 diff --git a/Firmware/Board/v3/Src/freertos.c b/Firmware/Board/v3/Src/freertos.c index 11154587..ac6c6de9 100644 --- a/Firmware/Board/v3/Src/freertos.c +++ b/Firmware/Board/v3/Src/freertos.c @@ -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 */ diff --git a/Firmware/Makefile b/Firmware/Makefile index 82ae758c..263ba433 100644 --- a/Firmware/Makefile +++ b/Firmware/Makefile @@ -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" diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 0ba614c3..80987c00 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -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(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; diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index dd8e88ef..9e78fdab 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -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(ctx)->update_watchdog_settings(); }, this), make_protocol_property("step_gpio_pin", &config_.step_gpio_pin, [](void* ctx) { static_cast(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) ); } }; diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 7ffa25b6..020f34d0 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -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); diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 4ea99133..d7235655 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -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: { diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index dff98f28..b3a0e3bd 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -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(&index_found_)), + make_protocol_property("is_ready", &is_ready_), + make_protocol_property("index_found", const_cast(&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(ctx)->set_idx_subscribe(); }, this), make_protocol_property("find_idx_on_lockin_only", &config_.find_idx_on_lockin_only, [](void* ctx) { static_cast(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(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), diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 7ffeb5e3..427792b3 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -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) { diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index df1bc14b..677fb996 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -77,7 +77,7 @@ struct BoardConfig_t { float brake_resistance = 0.47f; // [ohm] #endif float dc_bus_undervoltage_trip_level = 8.0f; // * ) } }; - SensorlessEstimator(Config_t& config); + explicit SensorlessEstimator(Config_t& config); bool update(); diff --git a/Firmware/MotorControl/trapTraj.hpp b/Firmware/MotorControl/trapTraj.hpp index 42dac0ef..fe5f3fec 100644 --- a/Firmware/MotorControl/trapTraj.hpp +++ b/Firmware/MotorControl/trapTraj.hpp @@ -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); diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 94cb080e..994fd746 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -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) diff --git a/Firmware/build.lua b/Firmware/build.lua index 8962e403..d4c7aad4 100644 --- a/Firmware/build.lua +++ b/Firmware/build.lua @@ -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 diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 8a0d3287..1e1c9ba4 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -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"); } diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index 4b503e68..498d5172 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -82,61 +82,19 @@ typedef struct { uint16_t endpoint_id; } endpoint_ref_t; +#include template::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 -inline size_t read_le(T* value, const uint8_t* buffer); - -template<> -inline size_t write_le(bool value, uint8_t* buffer) { - buffer[0] = value ? 1 : 0; - return 1; -} - -template<> -inline size_t write_le(uint8_t value, uint8_t* buffer) { - buffer[0] = value; - return 1; -} - -template<> -inline size_t write_le(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 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 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 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::value, size_t> +write_le(T value, uint8_t* buffer) { + return write_le>(value, buffer); } template<> @@ -148,65 +106,17 @@ inline size_t write_le(float value, uint8_t* buffer) { } template -typename std::enable_if_t::value, size_t> -write_le(T value, uint8_t* buffer) { - return write_le>(value, buffer); -} - -template<> -inline size_t read_le(bool* value, const uint8_t* buffer) { - *value = buffer[0]; - return 1; -} - -template<> -inline size_t read_le(uint8_t* value, const uint8_t* buffer) { - *value = buffer[0]; - return 1; -} - -template<> -inline size_t read_le(uint16_t* value, const uint8_t* buffer) { - *value = (static_cast(buffer[0]) << 0) | - (static_cast(buffer[1]) << 8); - return 2; -} - -template<> -inline size_t read_le(int32_t* value, const uint8_t* buffer) { - *value = (static_cast(buffer[0]) << 0) | - (static_cast(buffer[1]) << 8) | - (static_cast(buffer[2]) << 16) | - (static_cast(buffer[3]) << 24); - return 4; -} - -template<> -inline size_t read_le(uint32_t* value, const uint8_t* buffer) { - *value = (static_cast(buffer[0]) << 0) | - (static_cast(buffer[1]) << 8) | - (static_cast(buffer[2]) << 16) | - (static_cast(buffer[3]) << 24); - return 4; -} - -template<> -inline size_t read_le(uint64_t* value, const uint8_t* buffer) { - *value = (static_cast(buffer[0]) << 0) | - (static_cast(buffer[1]) << 8) | - (static_cast(buffer[2]) << 16) | - (static_cast(buffer[3]) << 24) | - (static_cast(buffer[4]) << 32) | - (static_cast(buffer[5]) << 40) | - (static_cast(buffer[6]) << 48) | - (static_cast(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* value, const uint8_t* buffer) { static_assert(CHAR_BIT * sizeof(float) == 32, "32 bit floating point expected"); static_assert(std::numeric_limits::is_iec559, "IEEE 754 floating point expected"); + return read_le(reinterpret_cast(value), buffer); } @@ -499,6 +409,14 @@ inline constexpr const char* get_default_json_modifier() { return "\"type\":\"float\",\"access\":\"rw\""; } template<> +inline constexpr const char* get_default_json_modifier() { + return "\"type\":\"int64\",\"access\":\"r\""; +} +template<> +inline constexpr const char* get_default_json_modifier() { + return "\"type\":\"int64\",\"access\":\"rw\""; +} +template<> inline constexpr const char* get_default_json_modifier() { return "\"type\":\"uint64\",\"access\":\"r\""; } diff --git a/Firmware/fibre/tupfiles/build.lua b/Firmware/fibre/tupfiles/build.lua index 4a0ea105..8b4a4510 100644 --- a/Firmware/fibre/tupfiles/build.lua +++ b/Firmware/fibre/tupfiles/build.lua @@ -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) diff --git a/Firmware/tup.config.default b/Firmware/tup.config.default index 60d2500d..5c2c4822 100644 --- a/Firmware/tup.config.default +++ b/Firmware/tup.config.default @@ -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 diff --git a/ODrive_Workspace.code-workspace b/ODrive_Workspace.code-workspace index d858dcc3..3d990eaf 100644 --- a/ODrive_Workspace.code-workspace +++ b/ODrive_Workspace.code-workspace @@ -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", diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 1c6d972e..860837ad 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -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) diff --git a/docs/ascii-protocol.md b/docs/ascii-protocol.md index 0c510fe6..d782aa79 100644 --- a/docs/ascii-protocol.md +++ b/docs/ascii-protocol.md @@ -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. diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 8083e16b..04671b48 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -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 ``` diff --git a/docs/getting-started.md b/docs/getting-started.md index 982ec861..8060bbb3 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -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. diff --git a/docs/odrivetool.md b/docs/odrivetool.md index c00d5bc3..52b648bb 100644 --- a/docs/odrivetool.md +++ b/docs/odrivetool.md @@ -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". diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 80e4475a..7795ed00 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -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 diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index fc5439bd..d4425a21 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -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 diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 28d1d737..e74376cf 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -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)